Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(docs-e2e): repair failing suite due to broken selectors #188

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions apps/docs-e2e/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// const playwright = require('eslint-plugin-playwright');
// const baseConfig = require('../../eslint.config.js');

// module.exports = [
// playwright.configs['flat/recommended'],
// ...baseConfig,
// {
// files: ['**/*.ts', '**/*.js'],
// // Override or add rules here
// rules: {},
// },
// ]

import playwright from 'eslint-plugin-playwright'
import baseConfigPromise from '../../eslint.config.js'

export default (async () => {
const baseConfig = await baseConfigPromise

return [
playwright.configs['flat/recommended'],
...baseConfig,
{
files: ['**.ts', '**.js'],
// Override or add rules here
rules: {},
},
]
})()
66 changes: 66 additions & 0 deletions apps/docs-e2e/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// TODO: Investigate node global process usage
/* eslint-disable node/prefer-global/process */
import { fileURLToPath } from 'node:url'
import { workspaceRoot } from '@nx/devkit'
import { nxE2EPreset } from '@nx/playwright/preset'

import { defineConfig, devices } from '@playwright/test'

const __filename = fileURLToPath(import.meta.url)

// For CI, you may want to set BASE_URL to the deployed application.
const baseURL = process.env.BASE_URL || 'http://127.0.0.1:3000'

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
...nxE2EPreset(__filename, { testDir: './src' }),
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
retries: 2,
use: {
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Run your local dev server before starting the tests */
webServer: {
command: 'pnpm nx start docs',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
cwd: workspaceRoot,
timeout: 120 * 1000,
},
projects: [
{
name: 'chromium (desktop)',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox (desktop)',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit (desktop)',
use: { ...devices['Desktop Safari'] },
},
{
name: 'webkit (tablet)',
use: { ...devices['iPad Mini'] },
},
{
name: 'chromium (mobile)',
use: { ...devices['Pixel 5'] },
},
{
name: 'webkit (mobile)',
use: { ...devices['iPhone 12'] },
},
],
})
8 changes: 8 additions & 0 deletions apps/docs-e2e/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "docs-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/docs-e2e/src",
"tags": [],
"// targets": "to see all targets run: nx show project docs-e2e --web",
"targets": {}
}
290 changes: 290 additions & 0 deletions apps/docs-e2e/src/docs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
import { test as base, expect } from '@playwright/test'
import { clickAndGoToPage } from './helpers/click-and-go-to-page'
import { DocsLayout } from './pom'

const test = base.extend<{ docsLayoutPage: DocsLayout }>({
docsLayoutPage: async ({ page }, use) => {
const docsLayoutPage = new DocsLayout(page)
await docsLayoutPage.goto()
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(docsLayoutPage)
},
})

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'

const CUHACKING_2025_GITHUB_PROJECT_BOARD_URL = `${GITHUB_ORG_BASE_URL}/projects/4`
const CUHACKING_2025_GITHUB_REPOSITORY_URL = `${GITHUB_BASE_URL}/2025`
const CUHACKING_2025_GITHUB_INDEX_PAGE_URL = `${GITHUB_BASE_URL}/2025/blob/main/apps/docs/content/docs/index.mdx`

const CUHACKING_2025_FOR_HACKERS_BY_HACKERS = `${GITHUB_BASE_URL}/2025/graphs/contributors`
const CUHACKING_2025_LANDING_PAGE_URL = 'https://cuhacking.ca/'
const CUHACKING_2025_LINKTREE_URL = 'https://linktr.ee/cuhacking_'
const CUHACKING_2025_PORTAL_URL = 'https://portal.cuhacking.ca/'

// TODO: Uncomment when the links are available - see tests below
// const CUHACKING_2025_DESIGN_URL = 'https://design.cuhacking.ca/'
// const CUHACKING_2025_ESLINT_URL = 'https://eslint.cuhacking.ca/rules'

const CUHACKING_2025_DISCORD_URL = 'https://discord.com/invite/h2cQqF9aZf'
const CUHACKING_2025_INSTAGRAM_URL = 'https://www.instagram.com/cuhacking/'

// TODO: Uncomment when the link works - see test below
// const CUHACKING_2025_LINKED_IN_URL = 'https://www.linkedin.com/company/cuhacking/'
const CUHACKING_2025_FIGMA_URL = 'https://www.figma.com/design/wc1JOWR48tBNkjcjwY3AzB/%E2%8C%A8%EF%B8%8F-cuHacking-Design-System?node-id=0-1&t=YTR1ET4Qw1wG1cjz-1'

/* ---------------- MOBILE + DESKTOP + TABLET ---------------- */
test.describe(`Common MOBILE, TABLET and DESKTOP Layout Elements`, {
tag: '@smoke',
}, () => {
test(`should contain title page`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.page).toHaveTitle(/Overview/)
})

test(`should contain cuHacking logo icon in header`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.cuHackingLogoIcon).toBeVisible()
})

test(`should take user to docs home page when cuHacking logo icon is clicked in header`, async ({ docsLayoutPage }) => {
await docsLayoutPage.cuHackingLogoIcon.click()
await expect(docsLayoutPage.page).toHaveURL(DOCS_BASE_URL)
})

test(`should contain cuHacking logo Text in header`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.cuHackingLogoText).toBeVisible()
})

test(`should take user to docs home page when cuHacking logo text is clicked in header`, async ({ docsLayoutPage }) => {
await docsLayoutPage.cuHackingLogoText.click()
await expect(docsLayoutPage.page).toHaveURL(DOCS_BASE_URL)
})

test(`should check for 'Next page' Button`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.nextButton).toBeVisible()
})

test(`should contain 'Edit on Github' button`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.editOnGithubButton).toBeVisible()
})

test(`should load index page when 'Edit on Github' is clicked`, async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.editOnGithubButton, CUHACKING_2025_GITHUB_INDEX_PAGE_URL)
})
})

/* ---------------- TABLET + DESKTOP + MOBILE LINKS---------------- */
test.describe('Common MOBILE, TABLET and DESKTOP Links', {
tag: '@smoke',
}, () => {
test.beforeEach(async ({ docsLayoutPage }) => {
const device = test.info().project.name
if (device.includes('mobile')) {
await docsLayoutPage.hamburgerIcon.click()
}
})
test('should contain website link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.websiteLink).toBeVisible()
})

test('should take user to website when website link clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.websiteLink, CUHACKING_2025_LANDING_PAGE_URL)
})

test('should contain Portal link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.portalLink).toBeVisible()
})

test('should take user to Portal when Portal link is clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.portalLink, CUHACKING_2025_PORTAL_URL)
})

test('should contain design link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.designLink).toBeVisible()
})

// TODO: Uncomment when the link is available

// test('should take user to design site when design link clicked', async ({ docsLayoutPage }) => {
// await clickAndGoToPage(docsLayoutPage, docsLayoutPage.designLink, CUHACKING_2025_DESIGN_URL)
// })

test('should contain Eslint link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.eslintLink).toBeVisible()
})
// TODO: Uncomment when the link is available

// test('should take user to eslint site when eslint link clicked', async ({ docsLayoutPage }) => {
// await clickAndGoToPage(docsLayoutPage, docsLayoutPage.eslintLink, CUHACKING_2025_ESLINT_URL)
// })

test('should contain discord link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.discordLink).toBeVisible()
})

test('should take user to Discord link site when Discord link clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.discordLink, CUHACKING_2025_DISCORD_URL)
})

test('should contain Instagram link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.instagramLink).toBeVisible()
})

test('should take user to Instagram when Instagram link clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.instagramLink, CUHACKING_2025_INSTAGRAM_URL)
})

test('should contain LinkedIn link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.linkedinLink).toBeVisible()
})

// TODO: LinkedIn link requires auth to view

// test('should take user to LinkedIn when LinkedIn link clicked', async ({ docsLayoutPage }) => {
// await clickAndGoToPage(docsLayoutPage, docsLayoutPage.linkedinLink, CUHACKING_2025_LINKED_IN_URL)
// })

test('should contain Linktree link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.linktreeLink).toBeVisible()
})

test('should take user to Linktree when Linktree link is clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.linktreeLink, CUHACKING_2025_LINKTREE_URL)
})

test('should contain Figma link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.figmaLink).toBeVisible()
})

test('should take user to Figma when Figma link is clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.figmaLink, CUHACKING_2025_FIGMA_URL)
})

test('should contain Project board link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.projectBoardLink).toBeVisible()
})

test('should take user to Project board when Project board link is clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.projectBoardLink, CUHACKING_2025_GITHUB_PROJECT_BOARD_URL)
})

test('should contain Github link', async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.githubLink).toBeVisible()
})

test('should take user to Github repo when Github link is clicked', async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.githubLink, CUHACKING_2025_GITHUB_REPOSITORY_URL)
})
})

/* ---------------- MOBILE + TABLET ---------------- */
test.describe(`Common MOBILE and TABLET Layout Elements`, {
tag: '@smoke',
}, () => {
test.beforeEach(async () => {
const device = test.info().project.name
if (device.includes('desktop')) {
test.skip()
}
})
test(`should have 'on this page' section visible in mobile/tablet header`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.onThisPage).toBeVisible()
})

test(`should have "For Hackers by Hackers" text visible in mobile/tablet for on this page`, async ({ docsLayoutPage }) => {
await docsLayoutPage.onThisPage.click()
await expect(docsLayoutPage.forHackersByHackers).toBeVisible()
})

test(`should click "For Hackers by Hackers" link mobile/tablet`, async ({ docsLayoutPage }) => {
await docsLayoutPage.onThisPage.click()
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.forHackersByHackers, CUHACKING_2025_FOR_HACKERS_BY_HACKERS)
})
})

/* ---------------- TABLET + DESKTOP ---------------- */
test.describe('Common TABLET and DESKTOP Layout Elements', {
tag: '@smoke',
}, () => {
test.beforeEach(async () => {
const device = test.info().project.name
if (device.includes('mobile')) {
test.skip()
}
})
test(`should contain Search Bar`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.searchBar).toBeVisible()
})

test(`should contain Search Modal`, async ({ docsLayoutPage }) => {
await docsLayoutPage.searchBar.click()
await expect(docsLayoutPage.searchModal).toBeVisible()
})

test(`should check for theme toggle`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.themeToggle).toBeVisible()
})

test(`should check for sidebar visibility`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.sideBarToggle).toBeVisible()
})

test(`should check for sidebar 'overview' visability`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.overviewSidebar).toBeVisible()
})
})

/* ---------------- UNIQUE MOBILE HEADER ---------------- */
test.describe('Unique MOBILE Header Elements', {
tag: '@smoke',
}, () => {
test.beforeEach(async () => {
const device = test.info().project.name
if (device.includes('desktop') || device.includes('tablet')) {
test.skip()
}
})
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()
})
})

/* ---------------- UNIQUE DESKTOP FLOATING TABLE ---------------- */
test.describe('Unique DESKTOP Floating Table Elements', {
tag: '@smoke',
}, () => {
test.beforeEach(async () => {
const device = test.info().project.name
if (device.includes('mobile') || device.includes('tablet')) {
test.skip()
}
})

test(`should have 'on this page' section visible in header`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.onThisPage).toBeVisible()
})

test(`should have "For Hackers by Hackers" text visible in for on this page`, async ({ docsLayoutPage }) => {
await expect(docsLayoutPage.forHackersByHackers).toBeVisible()
})

test(`should click "For Hackers by Hackers" link`, async ({ docsLayoutPage }) => {
await clickAndGoToPage(docsLayoutPage, docsLayoutPage.forHackersByHackers, CUHACKING_2025_FOR_HACKERS_BY_HACKERS)
})

// 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 }) => {
// FIXME: navigate to a different page, click the button, and check that the url is the corresponding file on the GitHub repo.
// Currently the test checks it against the index page instead of the current page. Also see TODO in `(docs)/page.tsx`.
// await clickAndGoToPage(docsLayoutPage, docsLayoutPage.editOnGitHubButton, CUHACKING_2025_PLATFORM_GITHUB_INDEX_PAGE_URL)
// })
})
Loading