Skip to content

Commit f6392b9

Browse files
shrabantipaul-collateShrabanti Paul
authored andcommitted
fix flaky search index application (#26795)
* fix flaky search index application * fix lint issues * address review comments --------- Co-authored-by: Shrabanti Paul <shrabantipaul@Shrabantis-MacBook-Pro.local>
1 parent c4bed4d commit f6392b9

1 file changed

Lines changed: 228 additions & 32 deletions

File tree

openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/SearchIndexApplication.spec.ts

Lines changed: 228 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,83 @@ import { settingClick } from '../../utils/sidebar';
2323
// use the admin user to login
2424
test.use({ storageState: 'playwright/.auth/admin.json' });
2525

26+
/**
27+
* Installs the Search Indexing Application from the marketplace.
28+
* Shared by the "Install application" step and the self-healing guard
29+
* that recovers from a previous retry leaving the app uninstalled.
30+
*/
31+
const installSearchIndexApplication = async (page: Page) => {
32+
const getMarketPlaceResponse = page.waitForResponse(
33+
'/api/v1/apps/marketplace?limit=15'
34+
);
35+
await page.click('[data-testid="add-application"]');
36+
37+
const response = await getMarketPlaceResponse;
38+
39+
expect(response.status()).toBe(200);
40+
41+
// Paginate through marketplace pages until the card is found.
42+
let cardFound = await page
43+
.locator('[data-testid="search-indexing-application-card"]')
44+
.isVisible();
45+
46+
while (!cardFound) {
47+
const nextButton = page.locator('[data-testid="next"]');
48+
const isNextButtonDisabled = await nextButton.isDisabled();
49+
50+
if (isNextButtonDisabled) {
51+
throw new Error(
52+
'search-indexing-application-card not found in marketplace and next button is disabled'
53+
);
54+
}
55+
56+
const nextPageResponse = page.waitForResponse('/api/v1/apps/marketplace*');
57+
await nextButton.click();
58+
await nextPageResponse;
59+
60+
cardFound = await page
61+
.locator('[data-testid="search-indexing-application-card"]')
62+
.isVisible();
63+
}
64+
65+
await page
66+
.getByTestId('search-indexing-application-card')
67+
.getByTestId('config-btn')
68+
.click();
69+
70+
await page.getByTestId('install-application').waitFor({ state: 'visible' });
71+
await page.getByTestId('install-application').click();
72+
73+
await page.getByTestId('save-button').waitFor({ state: 'visible' });
74+
await page.getByTestId('save-button').click();
75+
76+
await page.getByTestId('submit-btn').waitFor({ state: 'visible' });
77+
await page.getByTestId('submit-btn').click();
78+
await page.getByTestId('schedular-card-container').waitFor();
79+
await page
80+
.getByTestId('schedular-card-container')
81+
.getByText('On Demand')
82+
.click();
83+
84+
await expect(page.locator('[data-testid="cron-type"]')).not.toBeVisible();
85+
86+
const installApplicationResponse = page.waitForResponse('api/v1/apps');
87+
const getApplications = page.waitForRequest(
88+
(request) =>
89+
request.url().includes('/api/v1/apps?limit') && request.method() === 'GET'
90+
);
91+
await page.click('[data-testid="deploy-button"]');
92+
await installApplicationResponse;
93+
94+
await toastNotification(page, 'Application installed successfully');
95+
96+
await getApplications;
97+
98+
await expect(
99+
page.getByTestId('search-indexing-application-card')
100+
).toBeVisible();
101+
};
102+
26103
const verifyLastExecutionStatus = async (page: Page) => {
27104
const { apiContext } = await getApiContext(page);
28105

@@ -73,40 +150,33 @@ const verifyLastExecutionRun = async (page: Page, response: Response) => {
73150
test('Search Index Application', async ({ page }) => {
74151
test.slow();
75152

76-
await test.step('Visit Application page', async () => {
77-
await redirectToHomePage(page);
78-
await settingClick(page, GlobalSettingOptions.APPLICATIONS);
79-
});
80-
81-
await test.step('Verify last execution run', async () => {
82-
const statusAPI = page.waitForResponse(
83-
'/api/v1/apps/name/SearchIndexingApplication/status?offset=0&limit=1'
84-
);
85-
await page
86-
.locator(
87-
'[data-testid="search-indexing-application-card"] [data-testid="config-btn"]'
88-
)
89-
.click();
90-
const statusResponse = await statusAPI;
91-
92-
expect(statusResponse.status()).toBe(200);
93-
94-
await verifyLastExecutionRun(page, statusResponse);
95-
});
96-
97-
await test.step('View App Run Config', async () => {
98-
await page.getByTestId('app-historical-config').click();
99-
await page.waitForSelector('[role="dialog"].ant-modal');
153+
await test.step('Visit Application page', async () => {
154+
await redirectToHomePage(page);
100155

101-
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
156+
// If a previous retry left the app uninstalled, reinstall via API.
157+
const { apiContext } = await getApiContext(page);
158+
const appCheckResponse = await apiContext.get(
159+
'/api/v1/apps/name/SearchIndexingApplication'
160+
);
102161

103-
await expect(page.locator('.ant-modal-title')).toContainText(
104-
'Search Indexing Configuration'
105-
);
162+
if (appCheckResponse.status() === 404) {
163+
// appConfiguration must be passed so the Configuration tab renders in the UI.
164+
const marketplaceResponse = await apiContext.get(
165+
'/api/v1/apps/marketplace/name/SearchIndexingApplication'
166+
);
167+
const { appConfiguration } = await marketplaceResponse.json();
168+
169+
await apiContext.post('/api/v1/apps', {
170+
data: {
171+
name: 'SearchIndexingApplication',
172+
displayName: 'Search Indexing',
173+
appConfiguration,
174+
appSchedule: { scheduleTimeline: 'None' },
175+
},
176+
});
177+
}
106178

107-
await page.click('[data-testid="app-run-config-close"]');
108-
await page.waitForSelector('[role="dialog"].ant-modal', {
109-
state: 'detached',
179+
await settingClick(page, GlobalSettingOptions.APPLICATIONS);
110180
});
111181
});
112182

@@ -286,5 +356,131 @@ test('Search Index Application', async ({ page }) => {
286356

287357
await verifyLastExecutionRun(page, statusResponse);
288358
});
289-
}
359+
360+
await test.step('View App Run Config', async () => {
361+
await page.getByTestId('app-historical-config').click();
362+
await page.locator('[role="dialog"].ant-modal').waitFor();
363+
364+
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
365+
366+
await expect(page.locator('.ant-modal-title')).toContainText(
367+
'Search Indexing Configuration'
368+
);
369+
370+
await page.click('[data-testid="app-run-config-close"]');
371+
await page.locator('[role="dialog"].ant-modal').waitFor({
372+
state: 'detached',
373+
});
374+
});
375+
376+
await test.step('Edit application', async () => {
377+
await page.click('[data-testid="edit-button"]');
378+
await page.getByTestId('schedular-card-container').waitFor();
379+
await page
380+
.getByTestId('schedular-card-container')
381+
.getByText('On Demand')
382+
.click();
383+
384+
const deployResponse = page.waitForResponse('/api/v1/apps/*');
385+
await page.click('.ant-modal-body [data-testid="deploy-button"]');
386+
await deployResponse;
387+
388+
await toastNotification(page, 'Schedule saved successfully');
389+
390+
expect(await page.innerText('[data-testid="schedule-type"]')).toContain(
391+
'None'
392+
);
393+
394+
await page.click('[data-testid="configuration"]');
395+
396+
await expect(page.locator('#search-indexing-application')).toContainText(
397+
'Search Indexing Application'
398+
);
399+
400+
await expect(page.locator('form')).toContainText('Auto Tune');
401+
402+
await page.fill('#root\\/batchSize', '100');
403+
404+
await page.getByTestId('tree-select-widget').click();
405+
406+
// Bring table option to view in dropdown via searching for it
407+
await page
408+
.getByTestId('tree-select-widget')
409+
.getByRole('combobox')
410+
.fill('Table');
411+
412+
// uncheck the entity
413+
await page.getByRole('tree').getByTitle('Table').click();
414+
415+
// Need an outside click to close the dropdown
416+
await clickOutside(page);
417+
await page.locator('[for="root/searchIndexMappingLanguage"]').click();
418+
419+
await page
420+
.getByTestId('select-widget-root/searchIndexMappingLanguage')
421+
.click();
422+
423+
await expect(page.getByTestId('select-option-JP')).toBeVisible();
424+
425+
await page.getByTestId('select-option-JP').click();
426+
427+
const responseAfterSubmit = page.waitForResponse('/api/v1/apps/*');
428+
await page.click('[data-testid="submit-btn"]');
429+
await responseAfterSubmit;
430+
431+
await toastNotification(page, 'Configuration saved successfully');
432+
});
433+
434+
await test.step('Uninstall application', async () => {
435+
await page.click('[data-testid="manage-button"]');
436+
await page.click('[data-testid="uninstall-button-title"]');
437+
438+
const deleteRequest = page.waitForResponse(
439+
'/api/v1/apps/name/SearchIndexingApplication?hardDelete=true'
440+
);
441+
await page.click('[data-testid="save-button"]');
442+
await deleteRequest;
443+
444+
await toastNotification(page, 'Application uninstalled successfully');
445+
446+
const card1 = page.locator(
447+
'[data-testid="search-indexing-application-card"]'
448+
);
449+
450+
await expect(card1).toBeHidden();
451+
});
452+
453+
await test.step('Install application', async () => {
454+
await installSearchIndexApplication(page);
455+
});
456+
457+
if (process.env.PLAYWRIGHT_IS_OSS) {
458+
await test.step('Run application', async () => {
459+
test.slow(true); // Test time shouldn't exceed while re-fetching the history API.
460+
461+
await page.click(
462+
'[data-testid="search-indexing-application-card"] [data-testid="config-btn"]'
463+
);
464+
465+
const triggerPipelineResponse = page.waitForResponse(
466+
'/api/v1/apps/trigger/SearchIndexingApplication'
467+
);
468+
await page.click('[data-testid="run-now-button"]');
469+
470+
await triggerPipelineResponse;
471+
472+
await toastNotification(page, 'Application triggered successfully');
473+
474+
const statusAPI = page.waitForResponse(
475+
'/api/v1/apps/name/SearchIndexingApplication/status?offset=0&limit=1'
476+
);
477+
await page.reload();
478+
const statusResponse = await statusAPI;
479+
480+
expect(statusResponse.status()).toBe(200);
481+
482+
await verifyLastExecutionRun(page, statusResponse);
483+
});
484+
}
485+
});
290486
});

0 commit comments

Comments
 (0)