@@ -23,6 +23,83 @@ import { settingClick } from '../../utils/sidebar';
2323// use the admin user to login
2424test . 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+
26103const verifyLastExecutionStatus = async ( page : Page ) => {
27104 const { apiContext } = await getApiContext ( page ) ;
28105
@@ -73,40 +150,33 @@ const verifyLastExecutionRun = async (page: Page, response: Response) => {
73150test ( '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