Files
freeCodeCamp/e2e/module-reset.spec.ts
T
2026-05-28 18:56:39 +05:30

251 lines
8.3 KiB
TypeScript

import { exec } from 'child_process';
import { promisify } from 'util';
import { test, expect } from '@playwright/test';
import translations from '../client/i18n/locales/english/translations.json';
const execP = promisify(exec);
// Seed certified user before all tests to ensure progress exists
test.beforeAll(async () => {
await execP('node ./tools/scripts/seed/seed-demo-user --certified-user', {
cwd: process.cwd().replace('/e2e', '')
});
});
// Re-seed after each test to restore progress (in case a test modifies it)
test.afterEach(async () => {
await execP('node ./tools/scripts/seed/seed-demo-user --certified-user', {
cwd: process.cwd().replace('/e2e', '')
});
});
test.describe('Module reset - Block level (v8 grid layout)', () => {
test.beforeEach(async ({ page }) => {
// Navigate to a superblock where certified user has completed challenges
// Certified user has isRespWebDesignCert: true with completed challenges
await page.goto('/learn/2022/responsive-web-design');
});
test('should show reset button on block hover', async ({ page }) => {
// First block should have progress for certified user
const blockHeader = page.locator('.block-header-wrapper').first();
// Reset button should be hidden initially (opacity: 0)
const resetButton = blockHeader.locator('.block-reset-button');
await expect(resetButton).toHaveCSS('opacity', '0');
// Hover over the block header
await blockHeader.hover();
// Reset button should become visible on hover (opacity: 1 for enabled, 0.3 for disabled)
await expect(resetButton).toBeVisible();
});
test('should open reset modal when clicking reset button', async ({
page
}) => {
// Find a block with an enabled reset button (has progress)
const blockHeader = page
.locator('.block-header-wrapper')
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
.first();
await blockHeader.hover();
// Click the reset button
const resetButton = blockHeader.locator('.block-reset-button');
await resetButton.click();
// Modal should be visible
await expect(page.getByRole('dialog')).toBeVisible();
// Modal should contain the reset heading
await expect(
page.getByText(
translations.learn['reset-progress-description'].split("'")[0],
{ exact: false }
)
).toBeVisible();
});
test('should close modal when clicking cancel button', async ({ page }) => {
// Find a block with an enabled reset button
const blockHeader = page
.locator('.block-header-wrapper')
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
.first();
await blockHeader.hover();
await blockHeader.locator('.block-reset-button').click();
// Modal should be visible
await expect(page.getByRole('dialog')).toBeVisible();
// Click the cancel button
await page
.getByRole('button', {
name: translations.learn['reset-progress-nevermind']
})
.click();
// Modal should be hidden
await expect(page.getByRole('dialog')).toBeHidden();
});
test('should disable reset button until verification phrase is entered', async ({
page
}) => {
// Find a block with an enabled reset button
const blockHeader = page
.locator('.block-header-wrapper')
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
.first();
await blockHeader.hover();
await blockHeader.locator('.block-reset-button').click();
// Confirm button should be disabled initially
const confirmButton = page.getByRole('button', {
name: translations.learn['reset-progress-confirm']
});
await expect(confirmButton).toBeDisabled();
// Enter incorrect text
const verifyInput = page.getByRole('textbox');
await verifyInput.fill('wrong text');
await expect(confirmButton).toBeDisabled();
// Enter correct verification phrase
await verifyInput.fill(translations.learn['reset-progress-verify']);
await expect(confirmButton).toBeEnabled();
});
});
test.describe('Module reset - Full reset flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/learn/2022/responsive-web-design');
});
test('should reset block progress when confirmed', async ({ page }) => {
// Find a block with an enabled reset button (has progress)
const blockHeader = page
.locator('.block-header-wrapper')
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
.first();
// Open the reset modal
await blockHeader.hover();
await blockHeader.locator('.block-reset-button').click();
await expect(page.getByRole('dialog')).toBeVisible();
// Enter verification phrase
const verifyInput = page.getByRole('textbox');
await verifyInput.fill(translations.learn['reset-progress-verify']);
const confirmButton = page.getByRole('button', {
name: translations.learn['reset-progress-confirm']
});
await confirmButton.click();
await expect(
page.getByText(
translations.learn['reset-progress-success'].split("'")[0],
{ exact: false }
)
).toBeVisible();
await page
.getByRole('button', {
name: translations.learn['reset-progress-dismiss']
})
.click();
await expect(page.getByRole('dialog')).toBeHidden();
});
});
// Skip: full-stack-developer page is not built in dev environment
test.describe.skip('Module reset - Chapter level (v9 accordion layout)', () => {
test.beforeEach(async ({ page }) => {
// Navigate to a chapter-based superblock
await page.goto('/learn/full-stack-developer');
});
test('should show reset button on chapter hover', async ({ page }) => {
// Find a chapter header wrapper
const chapterHeader = page.locator('.chapter-header-wrapper').first();
// Reset button should be hidden initially
const resetButton = chapterHeader.locator('.block-reset-button');
await expect(resetButton).toHaveCSS('opacity', '0');
// Hover over the chapter header
await chapterHeader.hover();
// Reset button should become visible on hover
await expect(resetButton).toHaveCSS('opacity', '1');
});
test('should show reset button on module hover', async ({ page }) => {
// Expand a chapter first
const chapterButton = page.locator('.chapter-button-main').first();
await chapterButton.click();
// Find a module header wrapper
const moduleHeader = page.locator('.module-header-wrapper').first();
// Hover over the module header
await moduleHeader.hover();
// Reset button should become visible on hover
const resetButton = moduleHeader.locator('.block-reset-button');
await expect(resetButton).toHaveCSS('opacity', '1');
});
test('should open reset modal for chapter with correct title', async ({
page
}) => {
// Hover over a chapter to reveal the reset button
const chapterHeader = page.locator('.chapter-header-wrapper').first();
await chapterHeader.hover();
// Click the reset button
const resetButton = chapterHeader.locator('.block-reset-button');
await resetButton.click();
// Modal should be visible with the chapter name in the heading
await expect(page.getByRole('dialog')).toBeVisible();
});
});
test.describe('Module reset - Accessibility', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/learn/2022/responsive-web-design');
});
test('reset button should have proper aria-label', async ({ page }) => {
// Find a reset button and check its aria-label
const resetButton = page.locator('.block-reset-button').first();
const ariaLabel = await resetButton.getAttribute('aria-label');
// Should contain "Reset progress for"
expect(ariaLabel).toContain('Reset progress for');
});
test('modal should be keyboard accessible', async ({ page }) => {
// Find a block with an enabled reset button
const blockHeader = page
.locator('.block-header-wrapper')
.filter({ has: page.locator('.block-reset-button:not(:disabled)') })
.first();
await blockHeader.hover();
await blockHeader.locator('.block-reset-button').click();
// Modal should be visible
await expect(page.getByRole('dialog')).toBeVisible();
// Press Escape to close
await page.keyboard.press('Escape');
// Modal should be hidden
await expect(page.getByRole('dialog')).toBeHidden();
});
});