From 05a962b6a9380d0006541e171c84abe88c26d7e9 Mon Sep 17 00:00:00 2001 From: Sem Bauke Date: Mon, 3 Jun 2024 17:47:04 +0200 Subject: [PATCH] feat: convert navbar test to Playwright (#55034) Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> --- .../default/learn/common-components/navbar.ts | 61 --- e2e/header.spec.ts | 485 ++++++++++-------- 2 files changed, 264 insertions(+), 282 deletions(-) delete mode 100644 cypress/e2e/default/learn/common-components/navbar.ts diff --git a/cypress/e2e/default/learn/common-components/navbar.ts b/cypress/e2e/default/learn/common-components/navbar.ts deleted file mode 100644 index b87fe2d8c5e..00000000000 --- a/cypress/e2e/default/learn/common-components/navbar.ts +++ /dev/null @@ -1,61 +0,0 @@ -const navBarselectors = { - heading: "[data-test-label='landing-header']", - smallCallToAction: "[data-test-label='landing-small-cta']", - navigationLinks: '.nav-list', - avatarContainer: '.avatar-container', - defaultAvatar: '.avatar-container', - menuButton: '#toggle-button-nav', - avatarImage: '.avatar-container .avatar' -}; - -describe('Navbar when logged out', () => { - it('should have the sign in button on landing and /learn', () => { - cy.visit('/'); - - cy.contains(navBarselectors.smallCallToAction, 'Sign in'); - - cy.visit('/learn'); - cy.contains(navBarselectors.smallCallToAction, 'Sign in'); - }); -}); - -describe('Navbar Logged in', () => { - beforeEach(() => { - cy.login(); - cy.visit('/'); - }); - - it('Should render properly', () => { - cy.get('#universal-nav').should('be.visible'); - cy.get('#universal-nav').should('have.class', 'universal-nav'); - }); - - it( - 'Should take user to learn page when clicked on ' + 'the freeCodeCamp logo', - () => { - cy.get('#universal-nav-logo').within(() => { - cy.get('svg').click(); - }); - cy.url().should('include', '/learn'); - } - ); - - it('Should have `Profile` link when user is signed in', () => { - cy.get(navBarselectors.menuButton).click(); - cy.get(navBarselectors.navigationLinks).contains('Profile').click(); - cy.url().should('include', '/developmentuser'); - }); - - it('Should have a profile image with class `default-border`', () => { - cy.get(navBarselectors.avatarContainer).should( - 'have.class', - 'default-border' - ); - cy.get(navBarselectors.defaultAvatar).should('exist'); - }); - - it('Should have a profile image with dimensions that are <= 26px', () => { - cy.get(navBarselectors.avatarImage).invoke('width').should('lte', 26); - cy.get(navBarselectors.avatarImage).invoke('height').should('lte', 26); - }); -}); diff --git a/e2e/header.spec.ts b/e2e/header.spec.ts index 68270e3b68a..3bc8493d51c 100644 --- a/e2e/header.spec.ts +++ b/e2e/header.spec.ts @@ -1,3 +1,4 @@ +import { execSync } from 'child_process'; import { test, expect } from '@playwright/test'; import translations from '../client/i18n/locales/english/translations.json'; import { availableLangs, hiddenLangs, LangNames } from '../shared/config/i18n'; @@ -20,236 +21,278 @@ const headerComponentElements = { const examUrl = '/learn/foundational-c-sharp-with-microsoft/foundational-c-sharp-with-microsoft-certification-exam/foundational-c-sharp-with-microsoft-certification-exam'; -test.use({ storageState: 'playwright/.auth/certified-user.json' }); +test.describe('Header', () => { + test.use({ storageState: 'playwright/.auth/development-user.json' }); -test.beforeEach(async ({ page }) => { - await page.goto('/'); -}); + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); -test('Has link for skip content', async ({ page }) => { - const skipContent = page.getByTestId(headerComponentElements.skipContent); - await expect(skipContent).toBeVisible(); - await expect(skipContent).toHaveAttribute('href', '#content-start'); -}); + test.beforeAll(() => { + execSync('node ./tools/scripts/seed/seed-demo-user'); + }); -test('Renders universal nav by default', async ({ page }) => { - const universalNavigation = page.getByTestId( - headerComponentElements.universalNav - ); - const universalNavigationLogo = page.getByTestId( - headerComponentElements.universalNavLogo - ); - await expect(universalNavigation).toBeVisible(); - await expect(universalNavigationLogo).toBeVisible(); - await expect(universalNavigationLogo).toHaveAttribute('href', '/learn'); -}); + test.afterAll(() => { + execSync('node ./tools/scripts/seed/seed-demo-user certified-user'); + }); -test('Should display search in header on desktop and in menu on mobile', async ({ - page, - isMobile -}) => { - const searchInput = page.getByLabel(translations.search.label); - const menuButton = page.getByTestId(headerComponentElements.menuButton); + test('Has link for skip content', async ({ page }) => { + const skipContent = page.getByTestId(headerComponentElements.skipContent); + await expect(skipContent).toBeVisible(); + await expect(skipContent).toHaveAttribute('href', '#content-start'); + }); - if (isMobile) { - await expect(searchInput).toBeHidden(); - await menuButton.click(); - await expect(searchInput).toBeVisible(); - } else { - await expect(searchInput).toBeVisible(); - } -}); + test('Renders universal nav by default', async ({ page }) => { + const universalNavigation = page.getByTestId( + headerComponentElements.universalNav + ); + const universalNavigationLogo = page.getByTestId( + headerComponentElements.universalNavLogo + ); + await expect(universalNavigation).toBeVisible(); + await expect(universalNavigationLogo).toBeVisible(); + await expect(universalNavigationLogo).toHaveAttribute('href', '/learn'); + }); -test('Clicking the "Change Language" button should open the language list', async ({ - page -}) => { - const toggleLangButton = page.getByTestId( - headerComponentElements.toggleLangButton - ); - await expect(toggleLangButton).toBeVisible(); - await toggleLangButton.click(); - const langList = page.getByTestId(headerComponentElements.languageList); - await expect(langList).toBeVisible(); -}); + test('Should display search in header on desktop and in menu on mobile', async ({ + page, + isMobile + }) => { + const searchInput = page.getByLabel(translations.search.label); + const menuButton = page.getByTestId(headerComponentElements.menuButton); -test('The language list should contain a button for each available language', async ({ - page -}) => { - const locales = availableLangs.client.filter( - lang => !hiddenLangs.includes(lang) - ); - - const toggleLangButton = page.getByTestId( - headerComponentElements.toggleLangButton - ); - await expect(toggleLangButton).toBeVisible(); - await toggleLangButton.click(); - const langList = page.getByTestId(headerComponentElements.languageList); - await expect(langList).toBeVisible(); - - const langButtons = page.getByTestId(headerComponentElements.languageButton); - await expect(langButtons).toHaveCount(locales.length); - - for (let i = 0; i < locales.length; i++) { - const btn = langButtons.nth(i); - await expect(btn).toContainText(LangNames[locales[i]]); - } -}); - -test('Clicking the menu button should open the menu', async ({ page }) => { - const menuButton = page.getByTestId(headerComponentElements.menuButton); - const menu = page.getByTestId(headerComponentElements.menu); - await expect(menuButton).toBeVisible(); - await menuButton.click(); - await expect(menu).toBeVisible(); -}); - -test('Focusing on a menu item, and pressing Esc should close the menu and focus on the menu button', async ({ - page -}) => { - const menuButton = page.getByTestId(headerComponentElements.menuButton); - const menu = page.getByTestId(headerComponentElements.menu); - - await expect(menuButton).toBeVisible(); - await menuButton.click(); - - const link = menu.getByRole('link', { name: translations.buttons.donate }); - await link.focus(); - - await page.keyboard.press('Escape'); - await expect(menu).toBeHidden(); - - await expect(menuButton).toBeFocused(); -}); - -test('The menu should contain links to: donate, curriculum, forum, news, radio, contribute, and podcast', async ({ - page -}) => { - const menuButton = page.getByTestId(headerComponentElements.menuButton); - const menu = page.getByTestId(headerComponentElements.menu); - await expect(menuButton).toBeVisible(); - await menuButton.click(); - await expect(menu).toBeVisible(); - - const menuLinks = [ - { - name: translations.buttons.donate, - href: '/donate' - }, - { - name: translations.buttons.curriculum, - href: '/learn' - }, - { - name: translations.buttons.forum, - href: links.nav.forum - }, - { - name: translations.buttons.news, - href: links.nav.news - }, - { - name: translations.buttons.radio, - href: process.env.RADIO_LOCATION || 'https://coderadio.freecodecamp.org' - }, - { - name: translations.buttons.contribute, - href: links.nav.contribute - }, - { - name: translations.buttons.podcast, - href: links.nav.podcast + if (isMobile) { + await expect(searchInput).toBeHidden(); + await menuButton.click(); + await expect(searchInput).toBeVisible(); + } else { + await expect(searchInput).toBeVisible(); } - ]; - - for (const menuLink of menuLinks) { - const link = menu.getByRole('link', { name: menuLink.name }); - await expect(link).toBeVisible(); - await expect(link).toHaveAttribute('href', menuLink.href); - } -}); - -test('The menu should be able to change the theme', async ({ page }) => { - const menuButton = page.getByTestId(headerComponentElements.menuButton); - const menu = page.getByTestId(headerComponentElements.menu); - await menuButton.click(); - await expect(menu).toBeVisible(); - - const themeButton = menu.getByRole('button', { - name: 'Night Mode' - }); - await themeButton.click(); - - await expect(page.locator('body')).toHaveClass('dark-palette'); - - await expect(page.getByTestId('flash-message')).toContainText( - /We have updated your theme/ - ); - await expect(menu).toBeHidden(); - - await menuButton.click(); - await expect(menu).toBeVisible(); - - await themeButton.click(); - await expect(page.locator('body')).toHaveClass('light-palette'); -}); - -test('The header should contain an avatar', async ({ page }) => { - const avatarLink = page.locator('.avatar-nav-link'); - await expect(avatarLink).toBeVisible(); - await expect(avatarLink).toHaveAttribute('href', '/certifieduser'); - - const avatarContainer = page.locator('.avatar-container'); - await expect(avatarContainer).toHaveClass('avatar-container blue-border'); -}); - -test('Renders exam nav for Foundational C# with Microsoft exam', async ({ - page -}) => { - await page.goto(examUrl); - await page - .getByRole('button', { - name: translations.buttons['click-start-exam'] - }) - .click(); - await expect(page).toHaveURL(examUrl); - await expect(page.getByTestId(headerComponentElements.examNav)).toBeVisible(); - await expect( - page.getByTestId(headerComponentElements.examNavLogo) - ).toBeVisible(); -}); - -test('The Sign In button should redirect to api/signin', async ({ - browser -}) => { - // Sign out user in order to test Sign In button - const context = await browser.newContext({ - storageState: { cookies: [], origins: [] } - }); - const page = await context.newPage(); - await page.goto('/'); - - const signInButton = page.getByRole('link', { - name: translations.buttons['sign-in'] }); - const apiLocation = process.env.API_LOCATION || 'http://localhost:3000'; - - await expect(signInButton).toHaveAttribute('href', `${apiLocation}/signin`); -}); - -test('When the user is signed out, only certain elements should be visible', async ({ - browser -}) => { - const context = await browser.newContext({ - storageState: { cookies: [], origins: [] } + test('Clicking the "Change Language" button should open the language list', async ({ + page + }) => { + const toggleLangButton = page.getByTestId( + headerComponentElements.toggleLangButton + ); + await expect(toggleLangButton).toBeVisible(); + await toggleLangButton.click(); + const langList = page.getByTestId(headerComponentElements.languageList); + await expect(langList).toBeVisible(); + }); + + test('The language list should contain a button for each available language', async ({ + page + }) => { + const locales = availableLangs.client.filter( + lang => !hiddenLangs.includes(lang) + ); + + const toggleLangButton = page.getByTestId( + headerComponentElements.toggleLangButton + ); + await expect(toggleLangButton).toBeVisible(); + await toggleLangButton.click(); + const langList = page.getByTestId(headerComponentElements.languageList); + await expect(langList).toBeVisible(); + + const langButtons = page.getByTestId( + headerComponentElements.languageButton + ); + await expect(langButtons).toHaveCount(locales.length); + + for (let i = 0; i < locales.length; i++) { + const btn = langButtons.nth(i); + await expect(btn).toContainText(LangNames[locales[i]]); + } + }); + + test('Clicking the menu button should open the menu', async ({ page }) => { + const menuButton = page.getByTestId(headerComponentElements.menuButton); + const menu = page.getByTestId(headerComponentElements.menu); + await expect(menuButton).toBeVisible(); + await menuButton.click(); + await expect(menu).toBeVisible(); + }); + + test('Focusing on a menu item, and pressing Esc should close the menu and focus on the menu button', async ({ + page + }) => { + const menuButton = page.getByTestId(headerComponentElements.menuButton); + const menu = page.getByTestId(headerComponentElements.menu); + + await expect(menuButton).toBeVisible(); + await menuButton.click(); + + const link = menu.getByRole('link', { name: translations.buttons.donate }); + await link.focus(); + + await page.keyboard.press('Escape'); + await expect(menu).toBeHidden(); + + await expect(menuButton).toBeFocused(); + }); + + test('The menu should contain links to: donate, curriculum, forum, news, radio, contribute, and podcast', async ({ + page + }) => { + const menuButton = page.getByTestId(headerComponentElements.menuButton); + const menu = page.getByTestId(headerComponentElements.menu); + await expect(menuButton).toBeVisible(); + await menuButton.click(); + await expect(menu).toBeVisible(); + + const menuLinks = [ + { name: translations.buttons.profile, href: '/developmentuser' }, + { + name: translations.buttons.donate, + href: '/donate' + }, + { + name: translations.buttons.curriculum, + href: '/learn' + }, + { + name: translations.buttons.forum, + href: links.nav.forum + }, + { + name: translations.buttons.news, + href: links.nav.news + }, + { + name: translations.buttons.radio, + href: process.env.RADIO_LOCATION || 'https://coderadio.freecodecamp.org' + }, + { + name: translations.buttons.contribute, + href: links.nav.contribute + }, + { + name: translations.buttons.podcast, + href: links.nav.podcast + } + ]; + + for (const menuLink of menuLinks) { + const link = menu.getByRole('link', { name: menuLink.name }); + await expect(link).toBeVisible(); + await expect(link).toHaveAttribute('href', menuLink.href); + } + }); + + test('The menu should be able to change the theme', async ({ page }) => { + const menuButton = page.getByTestId(headerComponentElements.menuButton); + const menu = page.getByTestId(headerComponentElements.menu); + await menuButton.click(); + await expect(menu).toBeVisible(); + + const themeButton = menu.getByRole('button', { + name: 'Night Mode' + }); + await themeButton.click(); + + await expect(page.locator('body')).toHaveClass('dark-palette'); + + await expect(page.getByTestId('flash-message')).toContainText( + /We have updated your theme/ + ); + await expect(menu).toBeHidden(); + + await menuButton.click(); + await expect(menu).toBeVisible(); + + await themeButton.click(); + await expect(page.locator('body')).toHaveClass('light-palette'); + }); + + test('The header should contain an avatar', async ({ page }) => { + const avatarLink = page.getByRole('link', { name: 'Profile' }); + await expect(avatarLink).toBeVisible(); + await expect(avatarLink).toHaveAttribute('href', '/developmentuser'); + + const avatar = avatarLink.getByRole('img', { + name: 'Default Avatar', + includeHidden: true // the svg is aria-hidden + }); + await expect(avatar).toBeVisible(); + }); + + test('The Avatar should be less or equal to 26px', async ({ page }) => { + const avatar = page + .getByRole('link', { name: 'Profile' }) + .getByRole('img', { + name: 'Default Avatar', + includeHidden: true // the svg is aria-hidden + }); + + await expect(avatar).toBeVisible(); + const avatarSize = await avatar.boundingBox(); + expect(avatarSize?.width).toBeLessThanOrEqual(26); + expect(avatarSize?.height).toBeLessThanOrEqual(26); + }); + + test('The Sign In button should redirect to api/signin', async ({ + browser + }) => { + // Sign out user in order to test Sign In button + const context = await browser.newContext({ + storageState: { cookies: [], origins: [] } + }); + const page = await context.newPage(); + await page.goto('/'); + + const signInButton = page.getByRole('link', { + name: translations.buttons['sign-in'] + }); + + const apiLocation = process.env.API_LOCATION || 'http://localhost:3000'; + + await expect(signInButton).toHaveAttribute('href', `${apiLocation}/signin`); + }); + + test('When the user is signed out, only certain elements should be visible', async ({ + browser + }) => { + const context = await browser.newContext({ + storageState: { cookies: [], origins: [] } + }); + const page = await context.newPage(); + await page.goto('/'); + const signInButton = page + .getByTestId(headerComponentElements.signInButton) + .nth(0); + await expect(signInButton).toBeVisible(); + + const avatar = page + .getByRole('link', { name: 'Profile' }) + .getByRole('img', { + name: 'Default Avatar', + includeHidden: true // the svg is aria-hidden + }); + await expect(avatar).toBeHidden(); + }); +}); + +test.describe('Exam Header', () => { + test.use({ storageState: 'playwright/.auth/certified-user.json' }); + + test('Renders exam nav for Foundational C# with Microsoft exam', async ({ + page + }) => { + await page.goto(examUrl); + await page + .getByRole('button', { + name: translations.buttons['click-start-exam'] + }) + .click(); + await expect(page).toHaveURL(examUrl); + await expect( + page.getByTestId(headerComponentElements.examNav) + ).toBeVisible(); + await expect( + page.getByTestId(headerComponentElements.examNavLogo) + ).toBeVisible(); }); - const page = await context.newPage(); - await page.goto('/'); - const signInButton = page - .getByTestId(headerComponentElements.signInButton) - .nth(0); - await expect(signInButton).toBeVisible(); - await expect(page.locator('.avatar-nav-link')).toBeHidden(); - await expect(page.locator('.avatar-container')).toBeHidden(); });