diff --git a/apps/docs-e2e/src/docs.spec.ts b/apps/docs-e2e/src/docs.spec.ts index 95ced233..642a42ae 100644 --- a/apps/docs-e2e/src/docs.spec.ts +++ b/apps/docs-e2e/src/docs.spec.ts @@ -2,6 +2,8 @@ import { test as base, expect } from '@playwright/test' import { DocsLayout } from './pom' +import { clickAndGoToPage } from './helpers/click-and-go-to-page' + const test = base.extend<{ docsLayoutPage: DocsLayout }>({ docsLayoutPage: async ({ page }, use) => { const docsLayoutPage = new DocsLayout(page) @@ -10,35 +12,213 @@ const test = base.extend<{ docsLayoutPage: DocsLayout }>({ }, }) -const DOCS_SITE_URL = 'http://localhost:3000/docs' -const CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL = 'https://github.com/cuhacking/2025' -const CUHACKING_2025_PLATFORM_GITHUB_PROJECT_BOARD_URL = 'https://github.com/orgs/cuhacking/projects/4' +const GITHUB_BASE_URL = 'https://github.com/cuhacking' +const GITHUB_ORG_BASE_URL = 'https://github.com/orgs/cuhacking' +const DOCS_BASE_URL = 'http://localhost:3000' -test('should contain page title', async ({ docsLayoutPage }) => { - await expect(docsLayoutPage.page).toHaveTitle(/Welcome to the Docs/) -}) +const CUHACKING_2025_PLATFORM_GITHUB_PROJECT_BOARD_URL = `${GITHUB_ORG_BASE_URL}/projects/4` +const CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL = `${GITHUB_BASE_URL}/2025` +const CUHACKING_2025_PLATFORM_GITHUB_INDEX_PAGE_URL = `${GITHUB_BASE_URL}/2025/blob/main/apps/docs/src/content/docs/index.mdx` +const CUHACKING_2025_LANDING_PAGE_GITHUB_REPOSITORY_URL = `${GITHUB_BASE_URL}/landing-page` -test.describe('should contain desktop header elements', { - tag: '@smoke', -}, () => { - test('should contain cuHacking logo icon in desktop header', async ({ docsLayoutPage }) => { - await expect(docsLayoutPage.cuHackingLogoIcon).toBeVisible() +const CUHACKING_2025_DOCS_URL = `${DOCS_BASE_URL}/docs` + +const CUHACKING_2025_LANDING_PAGE_URL = 'https://www.cuhacking.ca/' + +const DEVICES: { DEVICE: string, VIEWPORT: { width: number, height: number } }[] = [ + { DEVICE: 'desktop', VIEWPORT: { width: 1024, height: 1440 } }, + { DEVICE: 'tablet', VIEWPORT: { width: 768, height: 1024 } }, + { DEVICE: 'mobile', VIEWPORT: { width: 320, height: 480 } }, +] + +const NARROW_DEVICES = DEVICES.filter(device => device.DEVICE !== 'desktop') +const WIDE_DEVICES = DEVICES.filter(device => device.DEVICE !== 'mobile') + +const MOBILE_DEVICE = DEVICES.find(device => device.DEVICE === 'mobile')! +const TABLET_DEVICE = DEVICES.find(device => device.DEVICE === 'tablet')! + +/* ---------------- MOBILE + DESKTOP + TABLET ---------------- */ +for (const { DEVICE, VIEWPORT } of DEVICES) { + test.describe(`[${DEVICE.toUpperCase()}] - Layout Elements`, { + tag: '@smoke', + }, () => { + test.beforeEach(async ({ docsLayoutPage }) => { + await docsLayoutPage.page.setViewportSize(VIEWPORT) + await docsLayoutPage.goto() + }) + + test(`should contain ${DEVICE} title page`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.page).toHaveTitle(/Welcome to the Docs/) + }) + + test(`should contain cuHacking logo icon in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.cuHackingLogoIcon).toBeVisible() + }) + + test(`should take user to docs home page when cuHacking logo icon is clicked in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await docsLayoutPage.cuHackingLogoIcon.click() + await expect(docsLayoutPage.page).toHaveURL(CUHACKING_2025_DOCS_URL) + }) + + test(`should contain cuHacking logo Text in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.cuHackingLogoText).toBeVisible() + }) + + test(`should take user to docs home page when cuHacking logo text is clicked in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await docsLayoutPage.cuHackingLogoText.click() + await expect(docsLayoutPage.page).toHaveURL(CUHACKING_2025_DOCS_URL) + }) + + test(`should contain last updated text in docs page footer for ${DEVICE}`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.lastUpdatedText).toBeVisible() + }) }) +} - test('should take user to docs home page when cuHacking logo icon is clicked', async ({ docsLayoutPage }) => { - await docsLayoutPage.cuHackingLogoIcon.click() - await expect(docsLayoutPage.page).toHaveURL(DOCS_SITE_URL) +/* ---------------- MOBILE + TABLET ---------------- */ +for (const { DEVICE, VIEWPORT } of NARROW_DEVICES) { + test.describe(`[${DEVICE.toUpperCase()}] - Layout Elements`, { + tag: '@smoke', + }, () => { + test.beforeEach(async ({ docsLayoutPage }) => { + await docsLayoutPage.page.setViewportSize(VIEWPORT) + await docsLayoutPage.goto() + }) + + test(`should have quick links section visible in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.quickLinks).toBeVisible() + }) + + test(`should have "Edit on Github" button visible in ${DEVICE} quick links`, async ({ docsLayoutPage }) => { + await docsLayoutPage.quickLinks.click() + await expect(docsLayoutPage.editOnGitHubButton).toBeVisible() + }) + + test(`should take user to github index page when "Edit on Github" button is clicked from ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.quickLinks.click() + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.editOnGitHubButton, CUHACKING_2025_PLATFORM_GITHUB_INDEX_PAGE_URL) + }) }) +} + +/* ---------------- MOBILE + TABLET MENU ---------------- */ +for (const { DEVICE, VIEWPORT } of NARROW_DEVICES) { + test.describe(`[${DEVICE.toUpperCase()}] - Menu Elements`, { + tag: '@smoke', + }, () => { + test.beforeEach(async ({ docsLayoutPage }) => { + await docsLayoutPage.goto() + await docsLayoutPage.page.setViewportSize(VIEWPORT) + if (DEVICE === 'mobile') { + await docsLayoutPage.hamburgerIcon.click() + } + else if (DEVICE === 'tablet') { + await docsLayoutPage.kebabIcon.click() + } + }) + + test(`should have docs pages section dropdown menu visible for ${DEVICE}`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.sectionsDropdownButton).toBeVisible() + }) + + test(`should have website button visible from website dropdown for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.websiteDropdownButton.click() + await expect(docsLayoutPage.mobileWebsiteLink).toBeVisible() + }) + + test(`should take user to landing page when website button is clicked for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.websiteDropdownButton.click() + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.mobileWebsiteLink, CUHACKING_2025_LANDING_PAGE_URL) + }) + + test(`should have website source button visible from website dropdown for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.websiteDropdownButton.click() + await expect(docsLayoutPage.mobileWebsiteSourceLink).toBeVisible() + }) + + test(`should take user to landing page repository when website source button is clicked for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.websiteDropdownButton.click() + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.mobileWebsiteSourceLink, CUHACKING_2025_LANDING_PAGE_GITHUB_REPOSITORY_URL) + }) + + test(`should have hacker portal app button visible from hacker portal dropdown for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.hackerPortalDropdownButton.click() + await expect(docsLayoutPage.mobileHackerAppLink).toBeVisible() + }) + + test(`should take user to hacker portal app when app button is clicked for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.hackerPortalDropdownButton.click() + await docsLayoutPage.mobileHackerAppLink.click() + await expect(docsLayoutPage.page).toHaveURL(DOCS_BASE_URL) + }) - test('should contain cuHacking logo Text in desktop header', async ({ docsLayoutPage }) => { - await expect(docsLayoutPage.cuHackingLogoText).toBeVisible() + test(`should have hacker portal source button visible from hacker portal dropdown for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.hackerPortalDropdownButton.click() + await expect(docsLayoutPage.mobileHackerPortalSourceLink).toBeVisible() + }) + + test(`should take user to hacker portal source repository when source button is clicked for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.hackerPortalDropdownButton.click() + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.mobileHackerPortalSourceLink, CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL) + }) + + test(`should have hacker portal project board button visible from hacker portal dropdown for ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.hackerPortalDropdownButton.click() + await expect(docsLayoutPage.mobileHackerPortalProjectBoardLink).toBeVisible() + }) + + test(`should take user to hacker portal project board when project board button is clicked ${DEVICE}`, async ({ docsLayoutPage }) => { + await docsLayoutPage.hackerPortalDropdownButton.click() + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.mobileHackerPortalProjectBoardLink, CUHACKING_2025_PLATFORM_GITHUB_PROJECT_BOARD_URL) + }) + + test(`should have github button visible from ${DEVICE} menu`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.mobileGithubIcon).toBeVisible() + }) + + test(`should take user to github repository when github button is clicked for ${DEVICE}`, async ({ docsLayoutPage }) => { + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.mobileGithubIcon, CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL) + }) + + test(`should have theme toggle visible for ${DEVICE}`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.themeToggle).toBeVisible() + }) }) +} + +/* ---------------- TABLET + DESKTOP ---------------- */ +for (const { DEVICE, VIEWPORT } of WIDE_DEVICES) { + test.describe(`[${DEVICE.toUpperCase()}] - Layout Elements`, { + tag: '@smoke', + }, () => { + test.beforeEach(async ({ docsLayoutPage }) => { + await docsLayoutPage.page.setViewportSize(VIEWPORT) + await docsLayoutPage.goto() + }) - test('should take user to docs home page when cuHacking logo text is clicked', async ({ docsLayoutPage }) => { - await docsLayoutPage.cuHackingLogoText.click() - await expect(docsLayoutPage.page).toHaveURL(DOCS_SITE_URL) + test(`should contain search bar in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.searchBar).toBeVisible() + }) + + test(`should show search modal when search bar clicked in ${DEVICE} header`, async ({ docsLayoutPage }) => { + await docsLayoutPage.searchBar.click() + await expect(docsLayoutPage.searchDialog).toBeVisible() + }) + + test(`should have docs pages section dropdown menu visible in ${DEVICE} sidebar`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.sectionsDropdownButton).toBeVisible() + }) + + test(`should contain sidebar toggle in ${DEVICE} sidebar`, async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.sideBarToggle).toBeVisible() + }) }) +} +/* ---------------- UNIQUE DESKTOP HEADER ---------------- */ +test.describe('[DESKTOP] - Header elements', { + tag: '@smoke', +}, () => { test('should contain Website dropdown button in desktop header', async ({ docsLayoutPage }) => { await expect(docsLayoutPage.websiteDropdownButton).toBeVisible() }) @@ -65,7 +245,7 @@ test.describe('should contain desktop header elements', { test('should take user to Hacker Portal App when Hacker Portal App link inside Hacker Portal Dropdown is clicked', async ({ docsLayoutPage }) => { await docsLayoutPage.hackerPortalDropdownButton.click() await docsLayoutPage.hackerPortalLink.click() - await expect(docsLayoutPage.page).toHaveURL('http://localhost:3000') + await expect(docsLayoutPage.page).toHaveURL(DOCS_BASE_URL) }) test('should contain Hacker Portal Source link inside Hacker Portal Dropdown', async ({ docsLayoutPage }) => { @@ -75,10 +255,7 @@ test.describe('should contain desktop header elements', { test('should take user to Hacker Portal GitHub Repository when Hacker Portal App link inside Hacker Portal Dropdown is clicked', async ({ docsLayoutPage }) => { await docsLayoutPage.hackerPortalDropdownButton.click() - const pagePromise = docsLayoutPage.page.context().waitForEvent('page') - await docsLayoutPage.hackerPortalSourceLink.click() - const newPage = await pagePromise - await expect(newPage).toHaveURL(CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL) + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.hackerPortalSourceLink, CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL) }) test('should contain Hacker Portal Project Board link inside Hacker Portal Dropdown', async ({ docsLayoutPage }) => { @@ -88,19 +265,7 @@ test.describe('should contain desktop header elements', { test('should take user to Hacker Portal Project Board when Hacker Portal App link inside Hacker Portal Dropdown is clicked', async ({ docsLayoutPage }) => { await docsLayoutPage.hackerPortalDropdownButton.click() - const pagePromise = docsLayoutPage.page.context().waitForEvent('page') - await docsLayoutPage.hackerPortalProjectBoardLink.click() - const newPage = await pagePromise - await expect(newPage).toHaveURL(CUHACKING_2025_PLATFORM_GITHUB_PROJECT_BOARD_URL) - }) - - test('should contain search bar in desktop header', async ({ docsLayoutPage }) => { - await expect(docsLayoutPage.searchBar).toBeVisible() - }) - - test('should show search modal when search bar in desktop header is clicked', async ({ docsLayoutPage }) => { - await docsLayoutPage.searchBar.click() - await expect(docsLayoutPage.searchDialog).toBeVisible() + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.hackerPortalProjectBoardLink, CUHACKING_2025_PLATFORM_GITHUB_PROJECT_BOARD_URL) }) test('should contain theme toggle in desktop header', async ({ docsLayoutPage }) => { @@ -112,22 +277,12 @@ test.describe('should contain desktop header elements', { }) test('should take user to cuHacking Hacker Portal GitHub repository when GitHub icon is clicked', async ({ docsLayoutPage }) => { - const pagePromise = docsLayoutPage.page.context().waitForEvent('page') - await docsLayoutPage.gitHubIcon.click() - const newPage = await pagePromise - await expect(newPage).toHaveURL(CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL) + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.gitHubIcon, CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL) }) }) -test.describe('should contain desktop sidebar elements', { - tag: '@smoke', -}, () => { - test('should contain sidebar toggle in desktop sidebar', async ({ docsLayoutPage }) => { - await expect(docsLayoutPage.sideBarToggle).toBeVisible() - }) -}) - -test.describe('should contain floating table of contents elements', { +/* ---------------- UNIQUE DESKTOP FLOATING TABLE ---------------- */ +test.describe('[DESKTOP]- Floating table of contents elements', { tag: '@smoke', }, () => { test('should contain \'Edit on GitHub\' button in floating table of contents', async ({ docsLayoutPage }) => { @@ -135,38 +290,43 @@ test.describe('should contain floating table of contents elements', { }) test('should take user to current docs page file on the cuHacking Hacker Portal GitHub repository when the \'Edit on GitHub\' icon is clicked', async ({ docsLayoutPage }) => { - const pagePromise = docsLayoutPage.page.context().waitForEvent('page') - await docsLayoutPage.editOnGitHubButton.click() - const newPage = await pagePromise - await expect(newPage).toHaveURL(`${CUHACKING_2025_PLATFORM_GITHUB_REPOSITORY_URL}/blob/main/src/content/docs/index.mdx`) + await clickAndGoToPage(docsLayoutPage, docsLayoutPage.editOnGitHubButton, CUHACKING_2025_PLATFORM_GITHUB_INDEX_PAGE_URL) }) }) -test.describe('should contain docs page elements', { +/* ---------------- UNIQUE MOBILE HEADER ---------------- */ +test.describe('[MOBILE] - Header elements', { tag: '@smoke', }, () => { - test('should contain last updated text in docs page footer', async ({ docsLayoutPage }) => { - await expect(docsLayoutPage.lastUpdatedText).toBeVisible() + test.beforeEach(async ({ docsLayoutPage }) => { + await docsLayoutPage.goto() + await docsLayoutPage.page.setViewportSize(MOBILE_DEVICE.VIEWPORT) + }) + + test('should contain hamburger icon', async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.hamburgerIcon).toBeVisible() + }) + + test('should contain search icon', async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.searchIcon).toBeVisible() + }) + + test('should show search modal when search icon is clicked', async ({ docsLayoutPage }) => { + await docsLayoutPage.searchIcon.click() + await expect(docsLayoutPage.searchModal).toBeVisible() }) }) -// TODO: complete test suite for mobile -test.describe('should contain mobile header elements', { +/* ---------------- UNIQUE TABLET HEADER ---------------- */ +test.describe('[TABLET] - Header elements', { tag: '@smoke', }, () => { - test('should contain cuHacking Logo Icon in mobile header', async ({ docsLayoutPage }) => { - await docsLayoutPage.page.setViewportSize({ - width: 320, - height: 480, - }) - await expect(docsLayoutPage.cuHackingLogoIcon).toBeVisible() + test.beforeEach(async ({ docsLayoutPage }) => { + await docsLayoutPage.goto() + await docsLayoutPage.page.setViewportSize(TABLET_DEVICE.VIEWPORT) }) - test('should contain hamburger icon in mobile header', async ({ docsLayoutPage }) => { - await docsLayoutPage.page.setViewportSize({ - width: 320, - height: 480, - }) - await expect(docsLayoutPage.hamburgerIcon).toBeVisible() + test('should have kebab icon visible', async ({ docsLayoutPage }) => { + await expect(docsLayoutPage.kebabIcon).toBeVisible() }) }) diff --git a/apps/docs-e2e/src/pom.ts b/apps/docs-e2e/src/pom.ts index 810b6507..b31a091b 100644 --- a/apps/docs-e2e/src/pom.ts +++ b/apps/docs-e2e/src/pom.ts @@ -22,6 +22,18 @@ export class DocsLayout { // Mobile Header readonly hamburgerIcon: Locator + readonly searchIcon: Locator + readonly searchModal: Locator + readonly quickLinks: Locator + + // Mobile + Tablet Elements + readonly sectionsDropdownButton: Locator + readonly mobileWebsiteLink: Locator + readonly mobileHackerPortalSourceLink: Locator + readonly mobileHackerAppLink: Locator + readonly mobileWebsiteSourceLink: Locator + readonly mobileGithubIcon: Locator + readonly mobileHackerPortalProjectBoardLink: Locator // Desktop Sidebar readonly sideBarToggle: Locator @@ -35,6 +47,9 @@ export class DocsLayout { // Search Dialog readonly searchDialog: Locator + // Tablet Header + readonly kebabIcon: Locator + constructor(page: Page) { this.page = page @@ -57,6 +72,18 @@ export class DocsLayout { // Mobile Header this.hamburgerIcon = page.getByLabel('Toggle Sidebar') + this.searchIcon = page.getByRole('button', { name: 'Open Search' }) + this.searchModal = page.getByPlaceholder('Search') + this.quickLinks = page.getByRole('button', { name: 'On this page Quick Links' }) + + // Mobile + Tablet Elements + this.sectionsDropdownButton = page.getByRole('button', { name: 'Home Leave none behind' }) + this.mobileWebsiteLink = page.getByRole('link', { name: 'Website' }) + this.mobileHackerPortalSourceLink = page.getByRole('link', { name: 'Source', exact: true }) + this.mobileHackerAppLink = page.getByRole('link', { name: 'App' }) + this.mobileWebsiteSourceLink = page.getByRole('link', { name: 'Source (legacy)' }) + this.mobileGithubIcon = page.getByRole('link', { name: 'Github' }) + this.mobileHackerPortalProjectBoardLink = page.getByRole('link', { name: 'Project Board' }) // Desktop Sidebar this.sideBarToggle = page.getByLabel('Collapse Sidebar') @@ -69,6 +96,9 @@ export class DocsLayout { // Search Dialog this.searchDialog = page.getByRole('dialog').getByPlaceholder('Search') + + // Tablet Header + this.kebabIcon = page.locator('#nd-nav').getByRole('button').nth(1) } async goto() {