From 24cc15c17994640cba057400a2ad5cb47b2b114a Mon Sep 17 00:00:00 2001 From: Muhammad Ali Date: Tue, 17 Jun 2025 12:13:15 +0500 Subject: [PATCH 1/4] updated tests --- .../components/OwnerAndReviewers/Owner.tsx | 13 +++++-- .../RequestTaskModal/RequestTaskModal.tsx | 3 +- static/client/global.d.ts | 7 ++++ tests/index.spec.ts | 19 ++++++++-- tests/project.spec.ts | 37 ++++++------------- tests/utils/common.ts | 29 +++++++++++++++ 6 files changed, 74 insertions(+), 34 deletions(-) create mode 100644 static/client/global.d.ts create mode 100644 tests/utils/common.ts diff --git a/static/client/components/OwnerAndReviewers/Owner.tsx b/static/client/components/OwnerAndReviewers/Owner.tsx index 8c18e868..e8c4134e 100644 --- a/static/client/components/OwnerAndReviewers/Owner.tsx +++ b/static/client/components/OwnerAndReviewers/Owner.tsx @@ -7,6 +7,7 @@ import CustomSearchAndFilter from "@/components/Common/CustomSearchAndFilter"; import IconTextWithTooltip from "@/components/Common/IconTextWithTooltip"; import config from "@/config"; import { PagesServices } from "@/services/api/services/pages"; +import { getDefaultUser } from "@/services/api/services/users"; import { type IUser } from "@/services/api/types/users"; const Owner = ({ page, onSelectOwner }: IOwnerAndReviewersProps): JSX.Element => { @@ -14,12 +15,18 @@ const Owner = ({ page, onSelectOwner }: IOwnerAndReviewersProps): JSX.Element => const { options, setOptions, handleChange } = useUsersRequest(); useEffect(() => { + let owner = null; + if (Boolean(window.__E2E_TESTING__)) { + owner = getDefaultUser(); + } + if (page) { - let owner = page.owner as IUser | null; + owner = page.owner as IUser | null; if (page.owner.name === "Default" || !page.owner.email) owner = null; - setCurrentOwner(owner); - if (onSelectOwner) onSelectOwner(owner); } + + setCurrentOwner(owner); + if (onSelectOwner) onSelectOwner(owner); }, [onSelectOwner, page]); const handleRemoveOwner = useCallback( diff --git a/static/client/components/RequestTaskModal/RequestTaskModal.tsx b/static/client/components/RequestTaskModal/RequestTaskModal.tsx index 394fdbaa..6a02e66f 100644 --- a/static/client/components/RequestTaskModal/RequestTaskModal.tsx +++ b/static/client/components/RequestTaskModal/RequestTaskModal.tsx @@ -260,7 +260,8 @@ const RequestTaskModal = ({ )} {((webpage.status !== PageStatus.NEW && changeType === ChangeRequestType.PAGE_REMOVAL) || - changeType !== ChangeRequestType.PAGE_REMOVAL) && ( + changeType !== ChangeRequestType.PAGE_REMOVAL || + window.__E2E_TESTING__) && ( )} diff --git a/static/client/global.d.ts b/static/client/global.d.ts new file mode 100644 index 00000000..e906769c --- /dev/null +++ b/static/client/global.d.ts @@ -0,0 +1,7 @@ +export {}; + +declare global { + interface Window { + __E2E_TESTING__?: boolean; + } +} diff --git a/tests/index.spec.ts b/tests/index.spec.ts index 941b9c7a..d59c8fcd 100644 --- a/tests/index.spec.ts +++ b/tests/index.spec.ts @@ -13,10 +13,21 @@ test.describe("Test Application Layout", () => { test("projects are loaded and visible", async ({ page }) => { await page.goto(`${config.BASE_URL}/app`); - const tree = await page.locator(".l-navigation__drawer .p-panel__content .p-list-tree").first(); - await expect(tree).toBeVisible(); - const children = await tree.locator(".p-list-tree__item"); - expect(await children.count()).toBeGreaterThan(0); + // select table view + await page.locator(".l-navigation__drawer .p-panel__content .p-side-navigation__link").first().click(); + + // check projects are present + const projects = page.locator(".p-accordion__list .p-accordion__group"); + const projectCount = await projects.count(); + expect(projectCount).toBeGreaterThan(0); + + // check all projects have pages + for (let i = 0; i < projectCount; i++) { + const project = projects.nth(i); + const projectHeading = project.locator(".p-accordion__heading"); + const projectPageCount = await projectHeading.locator(".p-badge").innerText(); + expect(parseInt(projectPageCount)).toBeGreaterThan(1); + } }); }); diff --git a/tests/project.spec.ts b/tests/project.spec.ts index 5d077fd9..dea029fc 100644 --- a/tests/project.spec.ts +++ b/tests/project.spec.ts @@ -1,6 +1,7 @@ import { test, expect, APIRequestContext } from "@playwright/test"; import { config } from "./config"; import type { IJiraTask } from "@/services/api/types/pages"; +import { removeWebpage } from "./utils/common"; const JIRA_TASKS: IJiraTask[] = []; let apiContext: APIRequestContext; @@ -13,42 +14,24 @@ test.describe("Test project actions", () => { }); test.beforeEach(async ({ page }) => { + await page.addInitScript(() => { + window.__E2E_TESTING__ = true; + }); + await page.setExtraHTTPHeaders({ "X-JIRA-REPORTER-ID": process.env.JIRA_REPORTER_ID || "", }); await page.goto(`${config.BASE_URL}/app`); + + // select tree view + await page.locator(".l-navigation__drawer .p-panel__content .p-side-navigation__link").nth(1).click(); }); test("remove page", async ({ page }) => { const tree = page.locator(".l-navigation__drawer .p-panel__content .p-list-tree").first(); const child = tree.locator(".p-list-tree__item").first(); await child.click(); - await expect(page.getByRole("heading", { name: /Title/i })).toBeVisible(); - page.getByRole("button", { name: /Request removal/i }).click(); - const modal = page.locator(".p-modal").first(); - await expect(modal).toBeVisible(); - await expect(page.getByRole("heading", { name: /Submit request for page removal/i })).toBeVisible(); - await modal.locator('input[type="date"]').fill(new Date().toISOString().split("T")[0]); - const checkboxes = await page.locator("input[type='checkbox'][required]"); - if (checkboxes) { - const checkboxCount = await checkboxes.count(); - for (let i = 0; i < checkboxCount; i++) { - await checkboxes.nth(i).check(); - } - } - - const responsePromise = page.waitForResponse((response) => response.url().includes("request-removal")); - await modal.getByRole("button", { name: /Submit/i }).click(); - const response = await responsePromise; - - if (response.status() === 200) { - const responseBody = await response.json(); - if (responseBody.jira_task_id) { - JIRA_TASKS.push(responseBody.jira_task_id); - } - } - - await expect(page.locator(".l-notification__container .p-notification--negative")).not.toBeVisible(); + await removeWebpage(page, JIRA_TASKS); }); test("request page changes", async ({ page }) => { @@ -131,6 +114,8 @@ test.describe("Test project actions", () => { } await expect(page.locator(".l-notification__container .p-notification--negative")).not.toBeVisible(); + + await removeWebpage(page, JIRA_TASKS); }); test.afterAll(async () => { diff --git a/tests/utils/common.ts b/tests/utils/common.ts new file mode 100644 index 00000000..336026ba --- /dev/null +++ b/tests/utils/common.ts @@ -0,0 +1,29 @@ +import { Page, expect } from "@playwright/test"; + +export async function removeWebpage(page: Page, JIRA_TASKS: string[]): Promise { + page.getByRole("button", { name: /Request removal/i }).click(); + const modal = page.locator(".p-modal").first(); + await expect(modal).toBeVisible(); + await expect(page.getByRole("heading", { name: /Submit request for page removal/i })).toBeVisible(); + await modal.locator('input[type="date"]').fill(new Date().toISOString().split("T")[0]); + const checkboxes = await page.locator("input[type='checkbox'][required]"); + if (checkboxes) { + const checkboxCount = await checkboxes.count(); + for (let i = 0; i < checkboxCount; i++) { + await checkboxes.nth(i).check(); + } + } + + const responsePromise = page.waitForResponse((response) => response.url().includes("request-removal")); + await modal.getByRole("button", { name: /Submit/i }).click(); + const response = await responsePromise; + + if (response.status() === 200) { + const responseBody = await response.json(); + if (responseBody.jira_task_id) { + JIRA_TASKS.push(responseBody.jira_task_id); + } + } + + await expect(page.locator(".l-notification__container .p-notification--negative")).not.toBeVisible(); +} From 30ddd6c40a00d09f012bdac4b711f9f0661df0f7 Mon Sep 17 00:00:00 2001 From: Muhammad Ali Date: Tue, 17 Jun 2025 13:20:49 +0500 Subject: [PATCH 2/4] fix failing test --- static/client/config/index.ts | 4 +++- tests/project.spec.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/static/client/config/index.ts b/static/client/config/index.ts index 71c3b6a0..e1c02a97 100644 --- a/static/client/config/index.ts +++ b/static/client/config/index.ts @@ -7,8 +7,10 @@ export const VIEW_REVIEWED = "reviewed"; export const VIEW_TREE = "tree"; export const VIEW_TABLE = "table"; +export const projects = ["canonical.com", "ubuntu.com", "cn.ubuntu.com", "jp.ubuntu.com"]; + const config = { - projects: ["canonical.com", "ubuntu.com", "cn.ubuntu.com", "jp.ubuntu.com"], + projects: window.__E2E_TESTING__ ? projects.slice(-2) : projects, views: [VIEW_OWNED, VIEW_REVIEWED, VIEW_TREE, VIEW_TABLE] as TView[], tooltips: { ownerDef: "Owners request the page and must approve the page for it to go live.", diff --git a/tests/project.spec.ts b/tests/project.spec.ts index dea029fc..7daa179a 100644 --- a/tests/project.spec.ts +++ b/tests/project.spec.ts @@ -114,7 +114,7 @@ test.describe("Test project actions", () => { } await expect(page.locator(".l-notification__container .p-notification--negative")).not.toBeVisible(); - + await page.waitForTimeout(5000); await removeWebpage(page, JIRA_TASKS); }); From 7aee96ea66ea9c931aab983f6c5570f7e71497e3 Mon Sep 17 00:00:00 2001 From: Muhammad Ali Date: Wed, 18 Jun 2025 11:20:42 +0500 Subject: [PATCH 3/4] added table view tests --- tests/index.spec.ts | 19 +++++++++------ tests/project.spec.ts | 5 +++- tests/utils/common.ts | 18 ++++++++++++++ tests/views/table-view.spec.ts | 43 ++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 tests/views/table-view.spec.ts diff --git a/tests/index.spec.ts b/tests/index.spec.ts index d59c8fcd..82abb4a0 100644 --- a/tests/index.spec.ts +++ b/tests/index.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "@playwright/test"; import { config } from "./config"; +import { selectTreeView } from "./utils/common"; test.describe("Test Application Layout", () => { test("displays the login page", async ({ page }) => { @@ -14,20 +15,24 @@ test.describe("Test Application Layout", () => { test("projects are loaded and visible", async ({ page }) => { await page.goto(`${config.BASE_URL}/app`); - // select table view - await page.locator(".l-navigation__drawer .p-panel__content .p-side-navigation__link").first().click(); + await selectTreeView(page); // check projects are present - const projects = page.locator(".p-accordion__list .p-accordion__group"); + const projectsDropdown = page.locator("select.l-site-selector"); + const projects = projectsDropdown.locator("option"); const projectCount = await projects.count(); expect(projectCount).toBeGreaterThan(0); // check all projects have pages for (let i = 0; i < projectCount; i++) { - const project = projects.nth(i); - const projectHeading = project.locator(".p-accordion__heading"); - const projectPageCount = await projectHeading.locator(".p-badge").innerText(); - expect(parseInt(projectPageCount)).toBeGreaterThan(1); + const value = await projects.nth(i).getAttribute("value"); + if (value) { + await projectsDropdown.selectOption(value); + const tree = page.locator(".l-navigation__drawer .p-panel__content .p-list-tree").first(); + const pages = tree.locator(".p-list-tree__item"); + const pageCount = await pages.count(); + expect(pageCount).toBeGreaterThan(0); + } } }); }); diff --git a/tests/project.spec.ts b/tests/project.spec.ts index 7daa179a..b7758336 100644 --- a/tests/project.spec.ts +++ b/tests/project.spec.ts @@ -1,7 +1,7 @@ import { test, expect, APIRequestContext } from "@playwright/test"; import { config } from "./config"; import type { IJiraTask } from "@/services/api/types/pages"; -import { removeWebpage } from "./utils/common"; +import { removeWebpage, selectTreeView } from "./utils/common"; const JIRA_TASKS: IJiraTask[] = []; let apiContext: APIRequestContext; @@ -28,6 +28,7 @@ test.describe("Test project actions", () => { }); test("remove page", async ({ page }) => { + await selectTreeView(page); const tree = page.locator(".l-navigation__drawer .p-panel__content .p-list-tree").first(); const child = tree.locator(".p-list-tree__item").first(); await child.click(); @@ -35,6 +36,7 @@ test.describe("Test project actions", () => { }); test("request page changes", async ({ page }) => { + await selectTreeView(page); const tree = page.locator(".l-navigation__drawer .p-panel__content .p-list-tree").first(); const child = tree.locator(".p-list-tree__item").first(); await child.click(); @@ -67,6 +69,7 @@ test.describe("Test project actions", () => { }); test("create new page", async ({ page }) => { + await selectTreeView(page); await page.getByRole("button", { name: /Request new page/i }).click(); await expect(page.getByRole("heading", { name: /New page/i })).toBeVisible(); await page.locator("input[aria-labelledby='url-title']").fill(config.PLAYWRIGHT_TEST_PAGE_URL); diff --git a/tests/utils/common.ts b/tests/utils/common.ts index 336026ba..18a898f5 100644 --- a/tests/utils/common.ts +++ b/tests/utils/common.ts @@ -27,3 +27,21 @@ export async function removeWebpage(page: Page, JIRA_TASKS: string[]): Promise { + const tableViewListItem = page.locator(".l-navigation__drawer .p-panel__content .p-side-navigation__link", { + hasText: /Table view/i, + }); + await tableViewListItem.click(); + + await page.getByText("/Loading projects. Please wait./i").waitFor({ state: "detached" }); +} + +export async function selectTreeView(page: Page): Promise { + const treeViewListItem = page.locator(".l-navigation__drawer .p-panel__content .p-side-navigation__link", { + hasText: /Tree view/i, + }); + await treeViewListItem.click(); + + await page.getByText("/Loading.../i").waitFor({ state: "detached" }); +} diff --git a/tests/views/table-view.spec.ts b/tests/views/table-view.spec.ts new file mode 100644 index 00000000..14a2b68b --- /dev/null +++ b/tests/views/table-view.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from "@playwright/test"; +import { config } from "../config"; +import { selectTableView } from "../utils/common"; + +test.beforeEach(async ({ page }) => { + await page.goto(`${config.BASE_URL}/app`); +}); + +test.describe("Test Table View", () => { + test("table view is visible", async ({ page }) => { + await selectTableView(page); + // expect a page showing all the projects in an accordion + expect(page.getByRole("heading", { name: /All pages/i })).toBeVisible(); + const projects = page.locator(".p-accordion__list .p-accordion__group"); + const projectCount = await projects.count(); + expect(projectCount).toBeGreaterThan(0); + + // check all projects have pages + for (let i = 0; i < projectCount; i++) { + const project = projects.nth(i); + const projectHeading = project.locator(".p-accordion__heading"); + const projectPageCount = await projectHeading.locator(".p-badge").innerText(); + expect(parseInt(projectPageCount)).toBeGreaterThan(1); + + // select each project + await project.click(); + + // select a random page + const pages = project.locator(".p-accordion__panel table tbody tr"); + const pagesCount = await pages.count(); + const selectedPage = project + .locator(".p-accordion__panel table tbody tr") + .nth(Math.floor(Math.random() * pagesCount)); + await selectedPage.locator(".p-button--link").first().click(); + + // check the page details are visible + await expect(page.getByText(/Description/i).first()).toBeVisible(); + + // click the back button + await page.getByRole("button", { name: /Back/i }).click(); + } + }); +}); From 66af00e292446282ff45199f58455f6ed2c78554 Mon Sep 17 00:00:00 2001 From: Muhammad Ali Date: Mon, 4 Aug 2025 16:48:02 +0500 Subject: [PATCH 4/4] Address comments --- README.md | 9 +++++++-- static/client/config/index.ts | 5 ++--- static/client/services/api/hooks/pages.ts | 3 ++- tests/config.ts | 1 - 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a56d6e8b..e692b507 100644 --- a/README.md +++ b/README.md @@ -367,13 +367,18 @@ Playwright is used to test different functionalities of the content system. Befo ``` JIRA_REPORTER_ID= FLASK_DEBUG=1 +DISABLE_SSO=1 ``` **Note**: -Replace `` with a valid reporter ID from JIra. This reporter will be used when creating Jira tasks as a result of running some tests. +Replace `` with a valid reporter ID from JIra. This reporter will be used when creating Jira tasks as a result of running some tests. -Replace `` with the ID of Canonical provided VPN which will be used to access Directory API for fetching users in different tests. +Update the following in `tests/config.ts` + +``` +BASE_URL: `http://localhost:${process.env.PORT}` +``` #### Running Playwright tests diff --git a/static/client/config/index.ts b/static/client/config/index.ts index e1c02a97..9cbb80c4 100644 --- a/static/client/config/index.ts +++ b/static/client/config/index.ts @@ -7,10 +7,9 @@ export const VIEW_REVIEWED = "reviewed"; export const VIEW_TREE = "tree"; export const VIEW_TABLE = "table"; -export const projects = ["canonical.com", "ubuntu.com", "cn.ubuntu.com", "jp.ubuntu.com"]; - const config = { - projects: window.__E2E_TESTING__ ? projects.slice(-2) : projects, + allProjects: ["canonical.com", "ubuntu.com", "cn.ubuntu.com", "jp.ubuntu.com"], + testProjects: ["canonical.com", "jp.ubuntu.com"], views: [VIEW_OWNED, VIEW_REVIEWED, VIEW_TREE, VIEW_TABLE] as TView[], tooltips: { ownerDef: "Owners request the page and must approve the page for it to go live.", diff --git a/static/client/services/api/hooks/pages.ts b/static/client/services/api/hooks/pages.ts index f7c0782d..af0967b5 100644 --- a/static/client/services/api/hooks/pages.ts +++ b/static/client/services/api/hooks/pages.ts @@ -8,8 +8,9 @@ import type { IPagesResponse } from "@/services/api/types/pages"; import type { IApiBasicError, IUseQueryHookRest } from "@/services/api/types/query"; export function usePages(noCache: boolean = false): IUseQueryHookRest { + let projects = window.__E2E_TESTING__ ? config.testProjects : config.allProjects; const results = useQueries[]>( - config.projects.map((project) => { + projects.map((project) => { return { queryKey: ["pages", project], queryFn: () => PagesServices.getPages(project, noCache).then((response) => response.data), diff --git a/tests/config.ts b/tests/config.ts index 06d5f5d5..b03e5f65 100644 --- a/tests/config.ts +++ b/tests/config.ts @@ -1,5 +1,4 @@ export const config = { BASE_URL: `http://localhost`, - PLAYWRIGHT_TEST_USER: "Muhammad Ali", // TODO: replace with a dedicated test user account name from IS PLAYWRIGHT_TEST_PAGE_URL: "playwright-test-page", };