diff --git a/src/frontend/apps/e2e/__tests__/app-impress/common.ts b/src/frontend/apps/e2e/__tests__/app-impress/common.ts index dfbcfb33..75100031 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/common.ts @@ -213,7 +213,26 @@ export const goToGridDoc = async ( return docTitle as string; }; -export const mockedDocument = async (page: Page, json: object) => { +export const updateDocTitle = async (page: Page, title: string) => { + const input = page.getByLabel('doc title input'); + await expect(input).toBeVisible(); + await expect(input).toHaveText(''); + await input.click(); + await input.fill(title); + await input.click(); + await verifyDocName(page, title); +}; + +export const waitForResponseCreateDoc = (page: Page) => { + return page.waitForResponse( + (response) => + response.url().includes('/documents/') && + response.url().includes('/children/') && + response.request().method() === 'POST', + ); +}; + +export const mockedDocument = async (page: Page, data: object) => { await page.route('**/documents/**/', async (route) => { const request = route.request(); if ( @@ -228,7 +247,7 @@ export const mockedDocument = async (page: Page, json: object) => { id: 'mocked-document-id', content: '', title: 'Mocked document', - accesses: [], + path: '000000', abilities: { destroy: false, // Means not owner link_configuration: false, @@ -239,11 +258,21 @@ export const mockedDocument = async (page: Page, json: object) => { update: false, partial_update: false, // Means not editor retrieve: true, + link_select_options: { + public: ['reader', 'editor'], + authenticated: ['reader', 'editor'], + restricted: null, + }, }, link_reach: 'restricted', + computed_link_reach: 'restricted', + computed_link_role: 'reader', + ancestors_link_reach: null, + ancestors_link_role: null, created_at: '2021-09-01T09:00:00Z', + user_role: 'owner', user_roles: ['owner'], - ...json, + ...data, }, }); } else { @@ -316,30 +345,32 @@ export const mockedAccesses = async (page: Page, json?: object) => { request.url().includes('page=') ) { await route.fulfill({ - json: { - count: 1, - next: null, - previous: null, - results: [ - { - id: 'bc8bbbc5-a635-4f65-9817-fd1e9ec8ef87', - user: { - id: 'b4a21bb3-722e-426c-9f78-9d190eda641c', - email: 'test@accesses.test', - }, - team: '', - role: 'reader', - abilities: { - destroy: true, - update: true, - partial_update: true, - retrieve: true, - set_role_to: ['administrator', 'editor'], - }, - ...json, + json: [ + { + id: 'bc8bbbc5-a635-4f65-9817-fd1e9ec8ef87', + user: { + id: 'b4a21bb3-722e-426c-9f78-9d190eda641c', + email: 'test@accesses.test', }, - ], - }, + team: '', + max_ancestors_role: null, + max_role: 'reader', + role: 'reader', + document: { + id: 'mocked-document-id', + path: '000000', + depth: 1, + }, + abilities: { + destroy: true, + update: true, + partial_update: true, + retrieve: true, + set_role_to: ['administrator', 'editor'], + }, + ...json, + }, + ], }); } else { await route.continue(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts index e3e41bf9..78b284b2 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-dnd.spec.ts @@ -212,6 +212,7 @@ const data = [ title: 'Can drop and drag', updated_at: '2025-03-14T14:45:27.699542Z', user_roles: ['owner'], + user_role: 'owner', }, { id: 'can-only-drop', @@ -260,6 +261,7 @@ const data = [ updated_at: '2025-03-14T14:45:27.699542Z', user_roles: ['editor'], + user_role: 'editor', }, { id: 'no-drop-and-no-drag', @@ -307,5 +309,6 @@ const data = [ title: 'No drop and no drag', updated_at: '2025-03-14T14:44:16.032774Z', user_roles: ['reader'], + user_role: 'reader', }, ]; diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts index 4c5eac3c..948b1a46 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts @@ -170,6 +170,7 @@ test.describe('Document grid item options', () => { link_reach: 'restricted', created_at: '2021-09-01T09:00:00Z', user_roles: ['editor'], + user_role: 'editor', }, ], }, diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index b33923fa..14f77344 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -109,6 +109,11 @@ test.describe('Doc Header', () => { versions_list: true, versions_retrieve: true, update: true, + link_select_options: { + public: ['reader', 'editor'], + authenticated: ['reader', 'editor'], + restricted: null, + }, partial_update: true, retrieve: true, }, @@ -134,7 +139,7 @@ test.describe('Doc Header', () => { await expect(shareModal).toBeVisible(); await expect(page.getByText('Share the document')).toBeVisible(); - await expect(page.getByPlaceholder('Type a name or email')).toBeVisible(); + // await expect(page.getByPlaceholder('Type a name or email')).toBeVisible(); const invitationCard = shareModal.getByLabel('List invitation card'); await expect(invitationCard).toBeVisible(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts new file mode 100644 index 00000000..f9e3e0a6 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts @@ -0,0 +1,208 @@ +import { expect, test } from '@playwright/test'; + +import { createDoc } from './common'; +import { + addMemberToDoc, + searchUserToInviteToDoc, + updateShareLink, + verifyLinkReachIsDisabled, + verifyLinkReachIsEnabled, + verifyLinkRoleIsDisabled, + verifyLinkRoleIsEnabled, + verifyMemberAddedToDoc, +} from './share-utils'; +import { createRootSubPage, createSubPageFromParent } from './sub-pages-utils'; + +test.describe('Inherited share accesses', () => { + test('Vérifie l’héritage des accès', async ({ page, browserName }) => { + await page.goto('/'); + const [titleParent] = await createDoc(page, 'root-doc', browserName, 1); + const docTree = page.getByTestId('doc-tree'); + + const addButton = page.getByRole('button', { name: 'New page' }); + // Wait for and intercept the POST request to create a new page + const responsePromise = page.waitForResponse( + (response) => + response.url().includes('/documents/') && + response.url().includes('/children/') && + response.request().method() === 'POST', + ); + await addButton.click(); + + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + const subPageJson = await response.json(); + + await expect(docTree).toBeVisible(); + const subPageItem = docTree + .getByTestId(`doc-sub-page-item-${subPageJson.id}`) + .first(); + + await expect(subPageItem).toBeVisible(); + await subPageItem.click(); + await page.getByRole('button', { name: 'Share' }).click(); + await expect(page.getByText('Inherited share')).toBeVisible(); + await expect(page.getByRole('link', { name: titleParent })).toBeVisible(); + await page.getByRole('button', { name: 'See access' }).click(); + await expect(page.getByText('Access inherited from the')).toBeVisible(); + const user = page.getByTestId( + `doc-share-member-row-user@${browserName}.e2e`, + ); + await expect(user).toBeVisible(); + await expect(user.getByText('E2E Chromium')).toBeVisible(); + await expect(user.getByText('Owner')).toBeVisible(); + }); + + test('Vérifie le message si il y a un accès hérité', async ({ + page, + browserName, + }) => { + await page.goto('/'); + await createDoc(page, 'root-doc', browserName, 1); + + // Search user to add + let users = await searchUserToInviteToDoc(page); + let userToAdd = users[0]; + + // Add user as Administrator in root doc + await addMemberToDoc(page, 'Administrator', [userToAdd]); + await verifyMemberAddedToDoc(page, userToAdd, 'Administrator'); + await page.getByRole('button', { name: 'OK' }).click(); + + // Create sub page + const { name: subPageName, item: subPageJson } = await createRootSubPage( + page, + browserName, + 'sub-page', + ); + + // Add user as Editor in sub page + users = await searchUserToInviteToDoc(page); + userToAdd = users[0]; + await addMemberToDoc(page, 'Editor', [userToAdd]); + const userRow = await verifyMemberAddedToDoc(page, userToAdd, 'Editor'); + await userRow.getByRole('button', { name: 'doc-role-dropdown' }).click(); + await page.getByText('This user has access').click(); + await userRow.click(); + await page.getByRole('button', { name: 'OK' }).click(); + + // Add new sub page to sub page + await createSubPageFromParent( + page, + browserName, + subPageJson.id, + 'sub-page-2', + ); + + // // Check sub page inherited share + await page.getByRole('button', { name: 'Share' }).click(); + await expect(page.getByText('Inherited share')).toBeVisible(); + await expect(page.getByRole('link', { name: subPageName })).toBeVisible(); + await page.getByRole('button', { name: 'See access' }).click(); + await expect(page.getByText('Access inherited from the')).toBeVisible(); + const user = page.getByTestId(`doc-share-member-row-${userToAdd.email}`); + await expect(user).toBeVisible(); + await expect(user.getByText('Administrator')).toBeVisible(); + }); +}); + +test.describe('Inherited share link', () => { + test('Vérifie si le lien est bien hérité', async ({ page, browserName }) => { + await page.goto('/'); + // Create root doc + await createDoc(page, 'root-doc', browserName, 1); + + // Update share link + await page.getByRole('button', { name: 'Share' }).click(); + await updateShareLink(page, 'Connected', 'Reading'); + await page.getByRole('button', { name: 'OK' }).click(); + + // Create sub page + await createRootSubPage(page, browserName, 'sub-page'); + + // // verify share link is restricted and reader + await page.getByRole('button', { name: 'Share' }).click(); + await expect(page.getByText('Inherited share')).toBeVisible(); + // await verifyShareLink(page, 'Connected', 'Reading'); + }); + + test('Vérification du message de warning lorsque les règles de partage diffèrent', async ({ + page, + browserName, + }) => { + await page.goto('/'); + // Create root doc + await createDoc(page, 'root-doc', browserName, 1); + + // Update share link + await page.getByRole('button', { name: 'Share' }).click(); + await updateShareLink(page, 'Connected', 'Reading'); + await page.getByRole('button', { name: 'OK' }).click(); + + // Create sub page + await createRootSubPage(page, browserName, 'sub-page'); + await page.getByRole('button', { name: 'Share' }).click(); + + // Update share link to public and edition + await updateShareLink(page, 'Public', 'Edition'); + await expect(page.getByText('Sharing rules differ from the')).toBeVisible(); + const restoreButton = page.getByRole('button', { name: 'Restore' }); + await expect(restoreButton).toBeVisible(); + await restoreButton.click(); + await expect( + page.getByText('The document visibility has been updated').first(), + ).toBeVisible(); + await expect(page.getByText('Sharing rules differ from the')).toBeHidden(); + }); + + test('Vérification des possibilités de liens hérités', async ({ + page, + browserName, + }) => { + await page.goto('/'); + // Create root doc + await createDoc(page, 'root-doc', browserName, 1); + + // Update share link + await page.getByRole('button', { name: 'Share' }).click(); + await updateShareLink(page, 'Connected', 'Reading'); + await page.getByRole('button', { name: 'OK' }).click(); + await expect( + page.getByText('Document accessible to any connected person'), + ).toBeVisible(); + + // Create sub page + const { item: subPageItem } = await createRootSubPage( + page, + browserName, + 'sub-page', + ); + await expect( + page.getByText('Document accessible to any connected person'), + ).toBeVisible(); + + // Update share link to public and edition + await page.getByRole('button', { name: 'Share' }).click(); + await verifyLinkReachIsDisabled(page, 'Private'); + await updateShareLink(page, 'Public', 'Edition'); + await page.getByRole('button', { name: 'OK' }).click(); + await expect(page.getByText('Public document')).toBeVisible(); + + // Create sub page + await createSubPageFromParent( + page, + browserName, + subPageItem.id, + 'sub-page-2', + ); + await expect(page.getByText('Public document')).toBeVisible(); + + // Verify share link and role + await page.getByRole('button', { name: 'Share' }).click(); + await verifyLinkReachIsDisabled(page, 'Private'); + await verifyLinkReachIsDisabled(page, 'Connected'); + await verifyLinkReachIsEnabled(page, 'Public'); + await verifyLinkRoleIsDisabled(page, 'Reading'); + await verifyLinkRoleIsEnabled(page, 'Edition'); + }); +}); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts index f92a7d4c..8e8a44a7 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts @@ -8,47 +8,59 @@ test.beforeEach(async ({ page }) => { test.describe('Document list members', () => { test('it checks a big list of members', async ({ page }) => { - await page.route( - /.*\/documents\/.*\/accesses\/\?page=.*/, - async (route) => { - const request = route.request(); - const url = new URL(request.url()); - const pageId = url.searchParams.get('page') ?? '1'; - - const accesses = { - count: 40, - next: +pageId < 2 ? 'http://anything/?page=2' : undefined, - previous: null, - results: Array.from({ length: 20 }, (_, i) => ({ - id: `2ff1ec07-86c1-4534-a643-f41824a6c53a-${pageId}-${i}`, - user: { - id: `fc092149-cafa-4ffa-a29d-e4b18af751-${pageId}-${i}`, - email: `impress@impress.world-page-${pageId}-${i}`, - full_name: `Impress World Page ${pageId}-${i}`, - }, - team: '', - role: 'editor', - abilities: { - destroy: false, - partial_update: true, - set_role_to: [], - }, - })), - }; - - if (request.method().includes('GET')) { - await route.fulfill({ - json: accesses, - }); - } else { - await route.continue(); - } - }, - ); - const docTitle = await goToGridDoc(page); await verifyDocName(page, docTitle); + // Get the current URL and extract the last part + const currentUrl = page.url(); + console.log('Current URL:', currentUrl); + const currentDocId = (() => { + // Remove trailing slash if present + const cleanUrl = currentUrl.endsWith('/') + ? currentUrl.slice(0, -1) + : currentUrl; + + // Split by '/' and get the last part + return cleanUrl.split('/').pop() || ''; + })(); + + await page.route('**/documents/**/accesses/', async (route) => { + const request = route.request(); + const url = new URL(request.url()); + const pageId = url.searchParams.get('page') ?? '1'; + + const accesses = Array.from({ length: 20 }, (_, i) => ({ + id: `2ff1ec07-86c1-4534-a643-f41824a6c53a-${pageId}-${i}`, + document: { + id: currentDocId, + name: `Doc ${pageId}-${i}`, + path: `0000.${pageId}-${i}`, + }, + user: { + id: `fc092149-cafa-4ffa-a29d-e4b18af751-${pageId}-${i}`, + email: `impress@impress.world-page-${pageId}-${i}`, + full_name: `Impress World Page ${pageId}-${i}`, + }, + team: '', + role: 'editor', + max_ancestors_role: null, + max_role: 'editor', + abilities: { + destroy: false, + partial_update: true, + set_role_to: ['administrator', 'editor'], + }, + })); + + if (request.method().includes('GET')) { + await route.fulfill({ + json: accesses, + }); + } else { + await route.continue(); + } + }); + await page.getByRole('button', { name: 'Share' }).click(); const prefix = 'doc-share-member-row'; @@ -56,11 +68,6 @@ test.describe('Document list members', () => { const loadMore = page.getByTestId('load-more-members'); await expect(elements).toHaveCount(20); - await expect(page.getByText(`Impress World Page 1-16`)).toBeVisible(); - - await loadMore.click(); - await expect(elements).toHaveCount(40); - await expect(page.getByText(`Impress World Page 2-15`)).toBeVisible(); await expect(loadMore).toBeHidden(); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts index 73534b27..e9f60677 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts @@ -25,7 +25,10 @@ test.describe('Document search', () => { ); await verifyDocName(page, doc2Title); await page.goto('/'); - await page.getByRole('button', { name: 'search' }).click(); + await page + .getByTestId('left-panel-desktop') + .getByRole('button', { name: 'search' }) + .click(); await expect( page.getByRole('img', { name: 'No active search' }), diff --git a/src/frontend/apps/e2e/__tests__/app-impress/share-utils.ts b/src/frontend/apps/e2e/__tests__/app-impress/share-utils.ts new file mode 100644 index 00000000..91062731 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/share-utils.ts @@ -0,0 +1,158 @@ +import { Locator, Page, expect } from '@playwright/test'; + +export type UserSearchResult = { + email: string; + full_name?: string | null; +}; + +export type Role = 'Administrator' | 'Owner' | 'Member' | 'Editor' | 'Reader'; +export type LinkReach = 'Private' | 'Connected' | 'Public'; +export type LinkRole = 'Reading' | 'Edition'; + +export const searchUserToInviteToDoc = async ( + page: Page, + inputFill?: string, +): Promise => { + const inputFillValue = inputFill ?? 'user '; + + const responsePromise = page.waitForResponse( + (response) => + response + .url() + .includes(`/users/?q=${encodeURIComponent(inputFillValue)}`) && + response.status() === 200, + ); + + await page.getByRole('button', { name: 'Share' }).click(); + const inputSearch = page.getByRole('combobox', { + name: 'Quick search input', + }); + await expect(inputSearch).toBeVisible(); + await inputSearch.fill(inputFillValue); + const response = await responsePromise; + const users = (await response.json()) as UserSearchResult[]; + return users; +}; + +export const addMemberToDoc = async ( + page: Page, + role: Role, + users: UserSearchResult[], +) => { + const list = page.getByTestId('doc-share-add-member-list'); + await expect(list).toBeHidden(); + const quickSearchContent = page.getByTestId('doc-share-quick-search'); + for (const user of users) { + await quickSearchContent + .getByTestId(`search-user-row-${user.email}`) + .click(); + } + + await list.getByLabel('doc-role-dropdown').click(); + await expect(page.getByLabel(role)).toBeVisible(); + await page.getByLabel(role).click(); + await page.getByRole('button', { name: 'Invite' }).click(); +}; + +export const verifyMemberAddedToDoc = async ( + page: Page, + user: UserSearchResult, + role: Role, +): Promise => { + const container = page.getByLabel('List members card'); + await expect(container).toBeVisible(); + const userRow = container.getByTestId(`doc-share-member-row-${user.email}`); + await expect(userRow).toBeVisible(); + await expect(userRow.getByText(role)).toBeVisible(); + await expect(userRow.getByText(user.full_name || user.email)).toBeVisible(); + return userRow; +}; + +export const updateShareLink = async ( + page: Page, + linkReach: LinkReach, + linkRole?: LinkRole | null, +) => { + await page.getByRole('button', { name: 'Visibility', exact: true }).click(); + await page.getByRole('menuitem', { name: linkReach }).click(); + + const visibilityUpdatedText = page + .getByText('The document visibility has been updated') + .first(); + + await expect(visibilityUpdatedText).toBeVisible(); + + if (linkRole) { + await page + .getByRole('button', { name: 'Visibility mode', exact: true }) + .click(); + await page.getByRole('menuitem', { name: linkRole }).click(); + await expect(visibilityUpdatedText).toBeVisible(); + } +}; + +export const verifyLinkReachIsDisabled = async ( + page: Page, + linkReach: LinkReach, +) => { + await page.getByRole('button', { name: 'Visibility', exact: true }).click(); + const item = page.getByRole('menuitem', { name: linkReach }); + await expect(item).toBeDisabled(); + await page.click('body'); +}; + +export const verifyLinkReachIsEnabled = async ( + page: Page, + linkReach: LinkReach, +) => { + await page.getByRole('button', { name: 'Visibility', exact: true }).click(); + const item = page.getByRole('menuitem', { name: linkReach }); + await expect(item).toBeEnabled(); + await page.click('body'); +}; + +export const verifyLinkRoleIsDisabled = async ( + page: Page, + linkRole: LinkRole, +) => { + await page + .getByRole('button', { name: 'Visibility mode', exact: true }) + .click(); + const item = page.getByRole('menuitem', { name: linkRole }); + await expect(item).toBeDisabled(); + await page.click('body'); +}; + +export const verifyLinkRoleIsEnabled = async ( + page: Page, + linkRole: LinkRole, +) => { + await page + .getByRole('button', { name: 'Visibility mode', exact: true }) + .click(); + const item = page.getByRole('menuitem', { name: linkRole }); + await expect(item).toBeEnabled(); + await page.click('body'); +}; + +export const verifyShareLink = async ( + page: Page, + linkReach: LinkReach, + linkRole?: LinkRole | null, +) => { + const visibilityDropdownButton = page.getByRole('button', { + name: 'Visibility', + exact: true, + }); + await expect(visibilityDropdownButton).toBeVisible(); + await expect(visibilityDropdownButton.getByText(linkReach)).toBeVisible(); + + if (linkRole) { + const visibilityModeButton = page.getByRole('button', { + name: 'Visibility mode', + exact: true, + }); + await expect(visibilityModeButton).toBeVisible(); + await expect(page.getByText(linkRole)).toBeVisible(); + } +}; diff --git a/src/frontend/apps/e2e/__tests__/app-impress/sub-pages-utils.ts b/src/frontend/apps/e2e/__tests__/app-impress/sub-pages-utils.ts new file mode 100644 index 00000000..40cd8495 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/sub-pages-utils.ts @@ -0,0 +1,76 @@ +import { Page, expect } from '@playwright/test'; + +import { randomName, updateDocTitle, waitForResponseCreateDoc } from './common'; + +export const createRootSubPage = async ( + page: Page, + browserName: string, + docName: string, +) => { + // Get add button + const addButton = page.getByRole('button', { name: 'New page' }); + + // Get response + const responsePromise = waitForResponseCreateDoc(page); + await addButton.click(); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + const subPageJson = (await response.json()) as { id: string }; + + // Get doc tree + const docTree = page.getByTestId('doc-tree'); + await expect(docTree).toBeVisible(); + + // Get sub page item + const subPageItem = docTree + .getByTestId(`doc-sub-page-item-${subPageJson.id}`) + .first(); + await expect(subPageItem).toBeVisible(); + await subPageItem.click(); + + // Update sub page name + const randomDocs = randomName(docName, browserName, 1); + await updateDocTitle(page, randomDocs[0]); + + // Return sub page data + return { name: randomDocs[0], docTreeItem: subPageItem, item: subPageJson }; +}; + +export const createSubPageFromParent = async ( + page: Page, + browserName: string, + parentId: string, + subPageName: string, +) => { + // Get parent doc tree item + const parentDocTreeItem = page.getByTestId(`doc-sub-page-item-${parentId}`); + await expect(parentDocTreeItem).toBeVisible(); + await parentDocTreeItem.hover(); + + // Create sub page + const responsePromise = waitForResponseCreateDoc(page); + await parentDocTreeItem.getByRole('button', { name: 'add_box' }).click(); + + // Get response + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + const subPageJson = (await response.json()) as { id: string }; + + // Get doc tree + const docTree = page.getByTestId('doc-tree'); + await expect(docTree).toBeVisible(); + + // Get sub page item + const subPageItem = docTree + .getByTestId(`doc-sub-page-item-${subPageJson.id}`) + .first(); + await expect(subPageItem).toBeVisible(); + await subPageItem.click(); + + // Update sub page name + const subPageTitle = randomName(subPageName, browserName, 1)[0]; + await updateDocTitle(page, subPageTitle); + + // Return sub page data + return { name: subPageTitle, docTreeItem: subPageItem, item: subPageJson }; +};