diff --git a/.gitignore b/.gitignore index 8718c4b83e2..3ebb8756df0 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ secret.env src/app/legacy/moment-timezone-include/tz tsconfig.tsbuildinfo src/service-worker-test.js +# playwright report files +__assets__ \ No newline at end of file diff --git a/ws-nextjs-app/package.json b/ws-nextjs-app/package.json index b21acd81b07..2678cf9c10a 100644 --- a/ws-nextjs-app/package.json +++ b/ws-nextjs-app/package.json @@ -31,6 +31,7 @@ "@cypress/webpack-preprocessor": "7.0.1", "@jest/environment": "30.2.0", "@jest/types": "30.2.0", + "@playwright/test": "1.52.0", "@swc/core": "1.15.2", "@testing-library/jest-dom": "6.9.1", "@testing-library/react": "16.3.1", @@ -47,6 +48,7 @@ "ts-node": "10.9.2" }, "dependencies": { + "@bbc/unified-web-e2e-framework": "github:bbc/unified-web-e2e-framework#v2.1.8", "next": "16.1.6", "sharp": "0.34.5" } diff --git a/ws-nextjs-app/playwright.config.ts b/ws-nextjs-app/playwright.config.ts new file mode 100644 index 00000000000..295235e5c48 --- /dev/null +++ b/ws-nextjs-app/playwright.config.ts @@ -0,0 +1,5 @@ +import { config } from '@bbc/unified-web-e2e-framework'; + +config.testDir = 'playwright/specs'; + +export default config; diff --git a/ws-nextjs-app/playwright/.eslintrc b/ws-nextjs-app/playwright/.eslintrc new file mode 100644 index 00000000000..fe91e993ad6 --- /dev/null +++ b/ws-nextjs-app/playwright/.eslintrc @@ -0,0 +1,7 @@ +{ + "rules": { + // these rules seem to be used as the common pattern for looping elemnts in playwright + "no-restricted-syntax": ["off", "ForOfStatement"], + "no-await-in-loop": "off", + }, +} diff --git a/ws-nextjs-app/playwright/README.md b/ws-nextjs-app/playwright/README.md new file mode 100644 index 00000000000..ecee7d60f13 --- /dev/null +++ b/ws-nextjs-app/playwright/README.md @@ -0,0 +1,4 @@ +# PlayWright + +To run the command +`npx playwright test --project="WorldService - Chrome"npx playwright test --project="WorldService - Chrome"` diff --git a/ws-nextjs-app/playwright/specs/homePage.spec.ts b/ws-nextjs-app/playwright/specs/homePage.spec.ts new file mode 100644 index 00000000000..5fad27f54de --- /dev/null +++ b/ws-nextjs-app/playwright/specs/homePage.spec.ts @@ -0,0 +1,72 @@ +/* eslint-disable cypress/no-async-tests */ +import { test, expect } from '@playwright/test'; +import runUrlValidationTest from '../support/helpers/runUrlValidationTest'; + +const testSuites = [ + { + path: '/arabic', + service: 'arabic', + variant: undefined, + }, + { + path: '/dari', + service: 'dari', + }, + { + path: '/kyrgyz', + service: 'kyrgyz', + }, + { + path: '/magyarul', + service: 'magyarul', + }, + { + path: '/polska', + service: 'polska', + }, + { + path: '/portuguese', + service: 'portuguese', + }, + { + path: '/romania', + service: 'romania', + }, + { + path: '/serbian/lat', + service: 'serbian', + variant: 'lat', + }, + { + path: '/serbian/cyr', + service: 'serbian', + variant: 'cyr', + }, + { + path: '/uzbek/lat', + service: 'uzbek', + variant: 'lat', + }, + { + path: '/uzbek/cyr', + service: 'uzbek', + variant: 'cyr', + }, +]; + +// smoke tests +testSuites.forEach(suite => { + test.describe(`Home Page - ${suite.service}${suite.variant ?? ''}`, () => { + test('all links within
element should be a valid World Service URL', async ({ + page, + }) => { + await page.goto(suite.path); + await expect(page).toHaveURL(suite.path); + runUrlValidationTest(page); + // ToDo: check how to reuse the same browser instead of opening / reponeing + // ToDO: run canonicalTests, testsForAllCanonicalPages + // ToDo: differentiate between smoke / non-smoke + // ToDo: decide whether to migrate runPage or do a different mroe direct approach (even if it means more repetition) + }); + }); +}); diff --git a/ws-nextjs-app/playwright/specs/setupCheck.spec.ts b/ws-nextjs-app/playwright/specs/setupCheck.spec.ts new file mode 100644 index 00000000000..a59feeeb271 --- /dev/null +++ b/ws-nextjs-app/playwright/specs/setupCheck.spec.ts @@ -0,0 +1,16 @@ +// import { test, expect } from '@bbc/unified-web-e2e-framework'; + +// test('Verify page title', async ({ page }) => { +// await page.goto('https://www.bbc.co.uk/'); +// expect(await page.title()).toBe('BBC - Home'); +// }); +import { test, expect } from '@playwright/test'; + +test.describe('Home Page - Arabic', () => { + test('should load and display expected content', async ({ page }) => { + await page.goto('/arabic'); + // Replace with actual selectors and checks + await expect(page.locator('h1')).toHaveText(/BBC News, عربي - الرئيسية/i); + // Add more assertions as needed + }); +}); diff --git a/ws-nextjs-app/playwright/support/helpers/runUrlValidationTest.ts b/ws-nextjs-app/playwright/support/helpers/runUrlValidationTest.ts new file mode 100644 index 00000000000..e7387fadc4e --- /dev/null +++ b/ws-nextjs-app/playwright/support/helpers/runUrlValidationTest.ts @@ -0,0 +1,21 @@ +import SERVICES from '#app/lib/config/services'; + +const SERVICES_PATTERN = SERVICES.join('|'); + +const VALID_HREF_REGEX = new RegExp( + `^https://www\\.bbc\\.com/(?:${SERVICES_PATTERN}|usingthebbc/[^/]+(?:/.*)?|programmes/[a-z0-9]{8,15})(?:/.*)?$`, +); + +export default page => { + test('all links within
element should be a valid World Service URL', async () => { + const links = await page + .locator('main a[href^="https://www.bbc.com"]') + .elementHandles(); + + for (const link of links) { + const href = await link.getAttribute('href'); + expect(href).not.toBeNull(); + expect(href).toMatch(VALID_HREF_REGEX); + } + }); +}; diff --git a/ws-nextjs-app/playwright/support/runtTestForPage.playwright.ts b/ws-nextjs-app/playwright/support/runtTestForPage.playwright.ts new file mode 100644 index 00000000000..41f0b4da57b --- /dev/null +++ b/ws-nextjs-app/playwright/support/runtTestForPage.playwright.ts @@ -0,0 +1,74 @@ +import { test as base, expect } from '@playwright/test'; + +type TestType = (props: any, page: any) => void; + +export type TestDataType = { + path: string; + tests: TestType[]; + runforEnv: string[]; + service: string; + contentType?: string; + applicationType?: string; + siteId?: string; + pageIdentifier?: string; +}; + +type FunctionProps = { + pageType: string; + testSuites: TestDataType[]; + beforeAll?: (() => void)[]; + beforeEachFns?: (() => void)[]; + testIsolation?: boolean; + headers?: Record; +}; + +export default function runTestsForPage({ + pageType, + testSuites, + beforeAll = [], + beforeEachFns = [], + testIsolation = false, + headers, +}: FunctionProps) { + const serviceToRun = process.env.ONLY_SERVICE; + + let testSuitesToRun = testSuites; + if (serviceToRun) { + testSuitesToRun = testSuites.filter( + ({ service }) => service === serviceToRun, + ); + } + + for (const testData of testSuitesToRun) { + const { path, tests, runforEnv, ...params } = testData; + const appEnv = process.env.APP_ENV ?? ''; + + if ( + Array.isArray(runforEnv) && + typeof appEnv === 'string' && + runforEnv.includes(appEnv) + ) { + base.describe(`${path}`, () => { + base.beforeAll(async ({ page }) => { + for (const fn of beforeAll) fn(); + await page.goto(path, { waitUntil: 'domcontentloaded', headers }); + }); + + base.beforeEach(async ({ page }) => { + for (const fn of beforeEachFns) fn(); + // Add any Playwright-specific setup here + }); + + const testParams = { + path, + pageType, + ...params, + }; + + for (const testFn of tests) { + testFn(testParams, base); + } + }); + } + } +} diff --git a/yarn.lock b/yarn.lock index ee83317e3ce..7da298d72b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2573,6 +2573,19 @@ __metadata: languageName: unknown linkType: soft +"@bbc/unified-web-e2e-framework@github:bbc/unified-web-e2e-framework#v2.1.8": + version: 2.1.8 + resolution: "@bbc/unified-web-e2e-framework@https://github.com/bbc/unified-web-e2e-framework.git#commit=a6e605858bc882e9db220bf57515f7a40dfd6779" + dependencies: + node-fetch: "npm:^2.7.0" + bin: + createTunnel: ./bin/createTunnel.sh + getBinaryUrl: ./bin/getBinaryUrl.sh + runTestsOnSauceLabs: ./bin/runTests.js + checksum: 10/3125d9d01c0c0b6b316c58c19e56efa397b19700f81861a5fa7ec4e8eb17597a2f3e35ced95328cb2026ef1625c1c74d3832c7522b8dbdd7b2a9cb6e69be590b + languageName: node + linkType: hard + "@bbc/web-vitals@npm:2.5.3": version: 2.5.3 resolution: "@bbc/web-vitals@npm:2.5.3" @@ -4967,6 +4980,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:1.52.0": + version: 1.52.0 + resolution: "@playwright/test@npm:1.52.0" + dependencies: + playwright: "npm:1.52.0" + bin: + playwright: cli.js + checksum: 10/e18a4eb626c7bc6cba212ff2e197cf9ae2e4da1c91bfdf08a744d62e27222751173e4b220fa27da72286a89a3b4dea7c09daf384d23708f284b64f98e9a63a88 + languageName: node + linkType: hard + "@polka/url@npm:^1.0.0-next.24": version: 1.0.0-next.29 resolution: "@polka/url@npm:1.0.0-next.29" @@ -11523,6 +11547,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: "npm:latest" + checksum: 10/6b5b6f5692372446ff81cf9501c76e3e0459a4852b3b5f1fc72c103198c125a6b8c72f5f166bdd76ffb2fca261e7f6ee5565daf80dca6e571e55bcc589cc1256 + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:^2.3.3, fsevents@npm:~2.3.2": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -11533,6 +11567,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" @@ -15697,6 +15740,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.52.0": + version: 1.52.0 + resolution: "playwright-core@npm:1.52.0" + bin: + playwright-core: cli.js + checksum: 10/42e13f5f98dc25ebc95525fb338a215b9097b2ba39d41e99972a190bf75d79979f163f5bc07b1ca06847ee07acb2c9b487d070fab67e9cd55e33310fc05aca3c + languageName: node + linkType: hard + +"playwright@npm:1.52.0": + version: 1.52.0 + resolution: "playwright@npm:1.52.0" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.52.0" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 10/214175446089000c2ac997b925063b95f7d86d129c5d7c74caa5ddcb05bcad598dfd569d2133a10dc82d288bf67e7858877dcd099274b0b928b9c63db7d6ecec + languageName: node + linkType: hard + "please-upgrade-node@npm:^3.2.0": version: 3.2.0 resolution: "please-upgrade-node@npm:3.2.0" @@ -17447,9 +17514,11 @@ __metadata: version: 0.0.0-use.local resolution: "simorgh-nextjs@workspace:ws-nextjs-app" dependencies: + "@bbc/unified-web-e2e-framework": "github:bbc/unified-web-e2e-framework#v2.1.8" "@cypress/webpack-preprocessor": "npm:7.0.1" "@jest/environment": "npm:30.2.0" "@jest/types": "npm:30.2.0" + "@playwright/test": "npm:1.52.0" "@swc/core": "npm:1.15.2" "@testing-library/jest-dom": "npm:6.9.1" "@testing-library/react": "npm:16.3.1"