voidmatcha commented on code in PR #5262:
URL: https://github.com/apache/zeppelin/pull/5262#discussion_r3342808846


##########
zeppelin-web-angular/e2e/utils.ts:
##########
@@ -327,121 +404,125 @@ export const navigateToNotebookWithFallback = async (
     throw new Error(`Failed to navigate to notebook ${noteId}`);
   }
 
-  // Wait for notebook to be ready
+  // Wait for notebook to be ready. Hash navigation can occasionally reach the
+  // target URL before the notebook component has subscribed to the backend 
data;
+  // a single reload keeps the same route while forcing Angular to fetch the 
note.
   await waitForZeppelinReady(page);
+  await waitForNotebookParagraphVisible(page, noteId);
 };
 
-const extractNoteIdFromUrl = async (page: Page): Promise<string | null> => {
-  const url = page.url();
-  const match = url.match(NOTEBOOK_PATTERNS.URL_EXTRACT_NOTEBOOK_ID_REGEX);
-  return match ? match[1] : null;
-};
+interface ZeppelinJsonResponse<T> {
+  status: string;
+  message?: string;
+  body: T;
+}
 
-const waitForNotebookNavigation = async (page: Page): Promise<string | null> 
=> {
-  await page.waitForURL(NOTEBOOK_PATTERNS.URL_REGEX, { timeout: 30000 });
-  return await extractNoteIdFromUrl(page);
-};
+interface InterpreterSettingSummary {
+  name?: string;
+}
 
-const navigateViaHomePageFallback = async (page: Page, baseNotebookName: 
string): Promise<string> => {
-  await page.goto('/#/');
-  await page.waitForLoadState('networkidle', { timeout: 15000 });
-  await page.waitForSelector('zeppelin-node-list', { timeout: 15000 });
+interface NoteSummary {
+  paragraphs?: Array<{ id?: string }>;
+}
 
-  await page.locator(NOTEBOOK_PATTERNS.LINK_SELECTOR).first().waitFor({ state: 
'attached', timeout: 15000 });
-  await page.waitForLoadState('domcontentloaded', { timeout: 15000 });
+const getDefaultInterpreterGroup = async (page: Page): Promise<string | 
undefined> => {
+  const response = await page.request.get('/api/interpreter/setting', { 
failOnStatusCode: false });
+  if (!response.ok()) {
+    return undefined;
+  }
 
-  const notebookLink = page.locator(NOTEBOOK_PATTERNS.LINK_SELECTOR).filter({ 
hasText: baseNotebookName });
+  const json = (await response.json()) as 
ZeppelinJsonResponse<InterpreterSettingSummary[]>;
+  return json.body?.find(setting => !!setting.name)?.name;
+};
 
-  const browserName = page.context().browser()?.browserType().name();
-  if (browserName === 'firefox') {
-    await 
page.waitForSelector(`${NOTEBOOK_PATTERNS.LINK_SELECTOR}:has-text("${baseNotebookName}")`,
 {
-      state: 'visible',
-      timeout: 90000
-    });
-  } else {
-    await notebookLink.waitFor({ state: 'visible', timeout: 60000 });
+const createNotebookViaRest = async (
+  page: Page,
+  notebookName: string
+): Promise<{ noteId: string; paragraphId: string }> => {
+  const defaultInterpreterGroup = await getDefaultInterpreterGroup(page);
+  const payload: Record<string, unknown> = {
+    notePath: notebookName,
+    addingEmptyParagraph: true
+  };
+
+  if (defaultInterpreterGroup) {
+    payload.defaultInterpreterGroup = defaultInterpreterGroup;
   }
 
-  await notebookLink.click({ timeout: 15000 });
-  await page.waitForURL(NOTEBOOK_PATTERNS.URL_REGEX, { timeout: 20000 });
+  const createResponse = await page.request.post('/api/notebook', {
+    data: payload,
+    failOnStatusCode: false
+  });
+  if (!createResponse.ok()) {
+    throw new Error(`Create notebook REST request failed: 
${createResponse.status()} ${await createResponse.text()}`);
+  }
 
-  const noteId = await extractNoteIdFromUrl(page);
+  const createJson = (await createResponse.json()) as 
ZeppelinJsonResponse<string>;
+  const noteId = createJson.body;
   if (!noteId) {
-    throw new Error('Failed to extract notebook ID after home page 
navigation');
+    throw new Error(`Create notebook REST response did not include note id: 
${JSON.stringify(createJson)}`);
   }
 
-  return noteId;
-};
-
-const extractFirstParagraphId = async (page: Page): Promise<string> => {
-  await page.locator('zeppelin-notebook-paragraph').first().waitFor({ state: 
'visible', timeout: 20000 });
+  let noteJson!: ZeppelinJsonResponse<NoteSummary>;
+  await expect(async () => {
+    const response = await page.request.get(`/api/notebook/${noteId}`, { 
failOnStatusCode: false });
+    if (!response.ok()) {
+      throw new Error(`Fetch notebook REST request failed: 
${response.status()} ${await response.text()}`);
+    }
+    noteJson = (await response.json()) as ZeppelinJsonResponse<NoteSummary>;
+  }).toPass({ timeout: 7500, intervals: [500, 1000, 1500, 2000, 2500] });
 
-  const paragraphContainer = 
page.locator('zeppelin-notebook-paragraph').first();
-  const dropdownTrigger = paragraphContainer.locator('a[nz-dropdown]');
-  await dropdownTrigger.click();
+  const paragraphId = noteJson.body?.paragraphs?.[0]?.id;
+  if (!paragraphId || !paragraphId.startsWith('paragraph_')) {
+    throw new Error(`Create notebook REST response did not include paragraph 
id: ${JSON.stringify(noteJson.body)}`);
+  }
 
-  const paragraphLink = page.locator('li.paragraph-id a').first();
-  await paragraphLink.waitFor({ state: 'attached', timeout: 15000 });
+  return { noteId, paragraphId };
+};

Review Comment:
   UI create-note is already covered by note-create-modal.spec.ts. Reusing it 
for setup would re-run the same Ant-modal + ngModel race in every spec's 
beforeEach. REST is deterministic and keeps that coverage in one spec.
   
   ---
   
   Rebased onto the latest master.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to