From 2f9db06133778f7ee37d68487a37c8cb47b99695 Mon Sep 17 00:00:00 2001 From: vitalets Date: Wed, 11 Dec 2024 20:50:09 +0400 Subject: [PATCH] test: html reporter move specs into features --- .../check-report/fixtures.ts | 12 +- .../check-report/playwright.config.ts | 2 +- .../check-report/specs/failing-after.test.ts | 140 ------------------ .../specs/failing-before-all.test.ts | 81 ---------- .../check-report/specs/failing-before.test.ts | 104 ------------- .../check-report/specs/failing-step.test.ts | 71 --------- .../failing-after-all/specs/todo.test.ts} | 4 +- .../failing-after/specs/error.feature.spec.ts | 84 +++++++++++ .../specs/timeout.feature.spec.ts | 51 +++++++ .../specs/anonymous.feature.spec.ts | 24 +++ .../specs/fixture.feature.spec.ts | 15 ++ .../failing-before-all/specs/helpers.ts | 5 + .../specs/named.feature.spec.ts | 17 +++ .../specs/timeout.feature.spec.ts | 14 ++ .../specs/error.feature.spec.ts | 61 ++++++++ .../specs/timeout.feature.spec.ts | 37 +++++ .../failing-bg/specs/error.feature.spec.ts} | 4 +- .../failing-step/specs/error.feature.spec.ts | 50 +++++++ .../specs/timeout.feature.spec.ts | 15 ++ .../success/specs/success.feature.spec.ts} | 4 +- .../{sample.feature => success.feature} | 0 .../playwright.config.ts | 1 + .../playwright.config.ts | 1 + 23 files changed, 391 insertions(+), 406 deletions(-) delete mode 100644 test/reporter-cucumber-html/check-report/specs/failing-after.test.ts delete mode 100644 test/reporter-cucumber-html/check-report/specs/failing-before-all.test.ts delete mode 100644 test/reporter-cucumber-html/check-report/specs/failing-before.test.ts delete mode 100644 test/reporter-cucumber-html/check-report/specs/failing-step.test.ts rename test/reporter-cucumber-html/{check-report/specs/failing-after-all.test.ts => features/failing-after-all/specs/todo.test.ts} (95%) create mode 100644 test/reporter-cucumber-html/features/failing-after/specs/error.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-after/specs/timeout.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-before-all/specs/anonymous.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-before-all/specs/fixture.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-before-all/specs/helpers.ts create mode 100644 test/reporter-cucumber-html/features/failing-before-all/specs/named.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-before-all/specs/timeout.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-before/specs/error.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-before/specs/timeout.feature.spec.ts rename test/reporter-cucumber-html/{check-report/specs/failing-bg.test.ts => features/failing-bg/specs/error.feature.spec.ts} (91%) create mode 100644 test/reporter-cucumber-html/features/failing-step/specs/error.feature.spec.ts create mode 100644 test/reporter-cucumber-html/features/failing-step/specs/timeout.feature.spec.ts rename test/reporter-cucumber-html/{check-report/specs/success.test.ts => features/success/specs/success.feature.spec.ts} (96%) rename test/reporter-cucumber-html/features/success/{sample.feature => success.feature} (100%) diff --git a/test/reporter-cucumber-html/check-report/fixtures.ts b/test/reporter-cucumber-html/check-report/fixtures.ts index 264e3a9b..25b214ba 100644 --- a/test/reporter-cucumber-html/check-report/fixtures.ts +++ b/test/reporter-cucumber-html/check-report/fixtures.ts @@ -3,6 +3,7 @@ import { test as base } from '@playwright/test'; import { HtmlReport } from './poms/HtmlReport'; import { Feature } from './poms/Feature'; import { Scenario } from './poms/Scenario'; +import path from 'node:path'; type Fixtures = { htmlReport: HtmlReport; @@ -18,11 +19,18 @@ export const test = base.extend({ }, htmlReport: async ({ page }, use) => use(new HtmlReport(page)), featureUri: ['', { option: true }], // will be overwritten in test files - feature: async ({ htmlReport, featureUri }, use) => { - if (!featureUri) throw new Error('Missing featureUri'); + feature: async ({ htmlReport }, use, testInfo) => { + const featureUri = getFeatureUriFromTestFile(testInfo.file); await use(htmlReport.getFeature(featureUri)); }, scenario: async ({ feature }, use, testInfo) => { await use(feature.getScenario(testInfo.title)); }, }); + +function getFeatureUriFromTestFile(file: string) { + const featureFilename = path.basename(file).replace(/\.spec\.ts$/, ''); + const featureDir = path.resolve(path.dirname(file), '..'); + const featurePath = path.join(featureDir, featureFilename); + return path.relative(process.cwd(), featurePath); +} diff --git a/test/reporter-cucumber-html/check-report/playwright.config.ts b/test/reporter-cucumber-html/check-report/playwright.config.ts index 21e534d6..c84b29f5 100644 --- a/test/reporter-cucumber-html/check-report/playwright.config.ts +++ b/test/reporter-cucumber-html/check-report/playwright.config.ts @@ -4,7 +4,7 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ - testDir: './specs', + testDir: '../features', outputDir: './test-results', reporter: 'dot', use: { diff --git a/test/reporter-cucumber-html/check-report/specs/failing-after.test.ts b/test/reporter-cucumber-html/check-report/specs/failing-after.test.ts deleted file mode 100644 index a9d8939e..00000000 --- a/test/reporter-cucumber-html/check-report/specs/failing-after.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { normalize } from 'node:path'; -import { expect } from '@playwright/test'; -import { test } from '../fixtures'; -import { getPackageVersion } from '../../../../src/utils'; - -// Automatic screenshot for failing fixtures teardown depends on pw version. -// see: https://github.com/microsoft/playwright/issues/29325 -const pwVersion = getPackageVersion('@playwright/test'); -const hasScreenshotAfterHookError = pwVersion >= '1.42.0'; - -test.describe('error', () => { - test.use({ featureUri: 'failing-after/error.feature' }); - - test('error in anonymous after hook', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText( - [ - 'Givenstep with page', - `Hook "AfterEach hook" failed: ${normalize('features/failing-after/steps.ts')}:`, // prettier-ignore - hasScreenshotAfterHookError ? 'screenshotDownload trace' : '', - ].filter(Boolean), - ); - await expect(scenario.getSteps('passed')).toHaveCount(1); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getTags()).toContainText(['@failing-anonymous-after-hook']); - await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); - }); - - test('error in named after hook', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText( - [ - 'Givenstep with page', - `Hook "failing named after hook" failed: ${normalize('features/failing-after/steps.ts')}:`, - hasScreenshotAfterHookError ? 'screenshotDownload trace' : '', - ].filter(Boolean), - ); - await expect(scenario.getSteps('passed')).toHaveCount(1); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getTags()).toContainText(['@failing-named-after-hook']); - await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); - }); - - test('error in fixture teardown (no step)', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'my attachment|before use', - 'Givenstep with page', - 'Givenstep that uses fixtureWithErrorInTeardown', - 'WhenAction 1', - `Hook "fixture: fixtureWithErrorInTeardown" failed: ${normalize('features/failing-after/fixtures.ts')}:`, - ]); - if (hasScreenshotAfterHookError) { - // position of screenshot item can vary - await expect(scenario.getSteps()).toContainText(['screenshot']); - } - await expect(scenario.getAttachments()).toContainText([ - 'my attachment|before use', // prettier-ignore - 'my attachment|after use', - ]); - await expect(scenario.getSteps('passed')).toHaveCount(3); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(0); - await expect(scenario.getErrors()).toContainText(['error in fixture teardown']); - }); - - test('error in fixture teardown (with step)', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'my attachment|outside step (before use)', - 'Givenstep with page', - 'Givenstep that uses fixtureWithErrorInTeardownStep', - 'WhenAction 1', - `Hook "step inside fixture" failed: ${normalize('features/failing-after/fixtures.ts')}:`, - 'my attachment|outside step (after use)', - ]); - if (hasScreenshotAfterHookError) { - // position of screenshot item can vary - await expect(scenario.getSteps()).toContainText(['screenshot']); - } - await expect(scenario.getAttachments()).toContainText([ - 'my attachment|outside step (before use)', - 'my attachment|in step', - 'my attachment|outside step (after use)', - ]); - await expect(scenario.getSteps('passed')).toHaveCount(3); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(0); - await expect(scenario.getErrors()).toContainText(['error in fixture teardown']); - }); -}); - -test.describe('timeout', () => { - test.use({ featureUri: 'failing-after/timeout.feature' }); - - test('timeout in fixture teardown', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'GivenAction 0', - 'Givenstep that uses fixtureWithTimeoutInTeardown', - 'WhenAction 1', - /Hook "(After Hooks|fixture: fixtureWithTimeoutInTeardown)" failed/, - ]); - // don't check screenshot as it's not reliable in timeouts - await expect(scenario.getSteps('passed')).toHaveCount(3); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getErrors()).toContainText([ - /but tearing down "fixtureWithTimeoutInTeardown" ran out of time|Tearing down "fixtureWithTimeoutInTeardown" exceeded the test timeout/, - ]); - }); - - test('timeout in step and in fixture teardown', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'GivenAction 0', - 'Giventimeouted step', - 'WhenAction 1', - 'Givenstep that uses fixtureWithTimeoutInTeardown', - /Hook "(After Hooks|fixture: fixtureWithTimeoutInTeardown)" failed/, - ]); - await expect(scenario.getSteps('passed')).toHaveCount(4); - // Don't check exact failed steps, it depends on PW version. - // - In modern PW versions (e.g. 1.49) timeouted fixture has 'error' field, we can find it. - // It shows: - // (1) Hook "fixture: fixtureWithTimeoutInTeardown" failed - // (2) Hook "After Hooks" failed: Test timeout of 1500ms exceeded - // - In older PW versions there is no 'error' field in timeouted fixture, we can't find it. - // It shows only fallback: - // (1) Hook "After Hooks" failed: Test timeout of 1500ms exceeded - await expect(scenario.getErrors()).toContainText([ - /Test timeout of \d+ms exceeded|Tearing down "fixtureWithTimeoutInTeardown" exceeded the test timeout/, - ]); - }); - - test('timeout in after hook', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'Givenstep with page', - // sometimes we still have "After Hooks" here, - // when duration = -1 is not in after hooks, and we cant detect which fixture timed out - /Hook "(my timeouted hook|After Hooks|fixture: \$afterEach)" failed/, - ]); - await expect(scenario.getErrors()).toContainText([ - /but tearing down "\$afterEach" ran out of time|Tearing down "\$afterEach" exceeded the test timeout/, - ]); - }); -}); diff --git a/test/reporter-cucumber-html/check-report/specs/failing-before-all.test.ts b/test/reporter-cucumber-html/check-report/specs/failing-before-all.test.ts deleted file mode 100644 index 2b6dc4cc..00000000 --- a/test/reporter-cucumber-html/check-report/specs/failing-before-all.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { normalize } from 'node:path'; -import { expect } from '@playwright/test'; -import { test } from '../fixtures'; - -import { getPackageVersion } from '../../../../src/utils'; - -// 'Download trace' appears in the report in case of error in before all hook since 1.42 -const pwVersion = getPackageVersion('@playwright/test'); -const hasDownloadTrace = pwVersion >= '1.42.0'; - -test.describe('error in anonymous before all hook', () => { - test.use({ featureUri: 'failing-before-all/anonymous.feature' }); - - test('scenario 1', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText( - [ - `Hook "BeforeAll hook" failed: ${normalize('features/failing-before-all/steps.ts')}:`, // prettier-ignore - 'GivenAction 1', - hasDownloadTrace ? 'Download trace' : '', - ].filter(Boolean), - ); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(1); - await expect(scenario.getErrors()).toContainText([`expect(true).toEqual(false)`]); - }); - - test('scenario 2', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'GivenAction 2', // prettier-ignore - ]); - await expect(scenario.getSteps('skipped')).toHaveCount(1); - }); -}); - -test.describe('error in named before all hook', () => { - test.use({ featureUri: 'failing-before-all/named.feature' }); - - test('scenario 1', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText( - [ - `Hook "my hook" failed: ${normalize('features/failing-before-all/steps.ts')}:`, // prettier-ignore - 'GivenAction 1', - hasDownloadTrace ? 'Download trace' : '', - ].filter(Boolean), - ); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(1); - await expect(scenario.getErrors()).toContainText([`expect(true).toEqual(false)`]); - }); -}); - -test.describe('error in worker fixture setup', () => { - test.use({ featureUri: 'failing-before-all/fixture.feature' }); - - test('scenario 1', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - `Hook "fixture: workerFixtureWithErrorInSetup" failed: ${normalize('features/failing-before-all/fixtures.ts')}:`, - 'GivenAction 1', - 'Givenstep that uses workerFixtureWithErrorInSetup', - 'Download trace', - ]); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(2); - await expect(scenario.getErrors()).toContainText(['error in worker fixture setup']); - }); -}); - -test.describe('timeout in before-all hook', () => { - test.use({ featureUri: 'failing-before-all/timeout.feature' }); - - test('scenario 1', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - `Hook "my timeouted hook" failed: ${normalize('features/failing-before-all/steps.ts')}:`, // prettier-ignore - 'GivenAction 1', - ]); - await expect(scenario.getErrors()).toContainText([ - // here can be different error messages - /("beforeAll" hook timeout of \d+ms exceeded)|(browser has been closed)|(Browser closed)/, - ]); - }); -}); diff --git a/test/reporter-cucumber-html/check-report/specs/failing-before.test.ts b/test/reporter-cucumber-html/check-report/specs/failing-before.test.ts deleted file mode 100644 index e216cebb..00000000 --- a/test/reporter-cucumber-html/check-report/specs/failing-before.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { normalize } from 'node:path'; -import { expect } from '@playwright/test'; -import { test } from '../fixtures'; - -test.describe('error', () => { - test.use({ featureUri: 'failing-before/error.feature' }); - - test('error in anonymous before hook', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - `Hook "BeforeEach hook" failed: ${normalize('features/failing-before/steps.ts')}:`, // prettier-ignore - 'GivenAction 1', - 'screenshotDownload trace', - ]); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(1); - await expect(scenario.getTags()).toContainText(['@failing-anonymous-before-hook']); - await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); - }); - - test('error in named before hook', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - `Hook "failing named before hook" failed: ${normalize('features/failing-before/steps.ts')}:`, - 'GivenAction 1', - 'screenshotDownload trace', - ]); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(1); - await expect(scenario.getTags()).toContainText(['@failing-named-before-hook']); - await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); - }); - - test('error in fixture setup (no step)', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - `Hook "fixture: fixtureWithErrorInSetup" failed: ${normalize('features/failing-before/fixtures.ts')}:`, - 'Givenstep that uses fixtureWithErrorInSetup', - 'WhenAction 1', - 'screenshot', - ]); - await expect(scenario.getAttachments()).toHaveText([ - 'my attachment|outside step', - 'my attachment|in step', - 'screenshot', - ]); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(2); - await expect(scenario.getErrors()).toContainText(['error in fixture setup']); - }); - - test('error in fixture setup (with step)', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - `Hook "step inside fixture" failed: ${normalize('features/failing-before/fixtures.ts')}:`, - 'Givenstep that uses fixtureWithErrorInSetupStep', - 'WhenAction 2', - 'screenshot', - ]); - await expect(scenario.getAttachments()).toHaveText([ - 'my attachment|in step', - 'my attachment|outside step', - 'screenshot', - ]); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(2); - await expect(scenario.getErrors()).toContainText(['error in fixture setup']); - }); -}); - -test.describe('timeout', () => { - test.use({ featureUri: 'failing-before/timeout.feature' }); - - test('timeout in fixture setup', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'GivenAction 0', - 'Givenstep that uses fixtureWithTimeoutInSetup', - 'WhenAction 1', - ]); - // 1. position of error message sometimes appears in After Hooks, so check it separately - // 2. here can be different error messages - await expect(scenario.getSteps()).toContainText([/Hook "(.+)" failed/]); - // screenshot position changes between PW versions, so check it separately - await expect(scenario.getSteps()).toContainText(['screenshot']); - // sometimes error is the following: - // "browser.newContext: Target page, context or browser has been closed" - // in that case there are two errors in test report. - expect(await scenario.getSteps('failed').count()).toBeGreaterThan(0); - await expect(scenario.getSteps('skipped')).toHaveCount(3); - await expect(scenario.getErrors()).toContainText([ - // here can be different error messages - /(Test timeout of \d+ms exceeded while setting up "fixtureWithTimeoutInSetup")|(browser has been closed)|(Browser closed)|(Page closed)/, - ]); - }); - - test('timeout in before hook', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - // sometimes we still have 'BeforeEach Hooks|Before Hooks' here, not 'my timeouted hook', - // when 'duration' is not -1, we can't find timeouted item. - /Hook "(my timeouted hook|BeforeEach Hooks|Before Hooks|fixture: \$beforeEach)" failed:/, // prettier-ignore - 'GivenAction 1', - ]); - await expect(scenario.getErrors()).toContainText([ - // here can be different error messages - /(Test timeout of \d+ms exceeded while running "beforeEach" hook)|(browser has been closed)|(Browser closed)|(Page closed)/, - ]); - }); -}); diff --git a/test/reporter-cucumber-html/check-report/specs/failing-step.test.ts b/test/reporter-cucumber-html/check-report/specs/failing-step.test.ts deleted file mode 100644 index fc438f01..00000000 --- a/test/reporter-cucumber-html/check-report/specs/failing-step.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { expect } from '@playwright/test'; -import { test } from '../fixtures'; - -test.describe('error', () => { - test.use({ featureUri: 'failing-step/error.feature' }); - - test('error in step', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'Givenstep with page', // prettier-ignore - 'Givenfailing step', - 'WhenAction 1', - 'screenshotDownload trace', - ]); - await expect(scenario.getSteps('passed')).toHaveCount(1); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(1); - await expect(scenario.getErrors()).toContainText(['expect(true).toBe(false)']); - }); - - test('failing match snapshot', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'Whenstep with page', - 'Thenerror in match snapshot', - ]); - await expect(scenario.getAttachments()).toHaveText([ - 'error-in-step-failing-match-snapshot-1-expected.txtbla-bla', - 'error-in-step-failing-match-snapshot-1-actual.txtExample Domain', - 'screenshot', - ]); - await expect(scenario.getSteps('passed')).toHaveCount(1); - await expect(scenario.getSteps('failed')).toHaveCount(1); - await expect(scenario.getSteps('skipped')).toHaveCount(0); - await expect(scenario.getErrors()).toContainText(['toMatchSnapshot']); - }); - - test('soft assertions', async ({ scenario }) => { - await expect(scenario.getSteps()).toHaveText([ - /Givenfailing soft assertion "foo"/, - 'AndAction 1', - /Andfailing soft assertion "bar"/, - 'AndAction 2', - // not 'screenshot', b/c no page - // 'screenshot', - 'Download trace', - ]); - await expect(scenario.getSteps('passed')).toHaveCount(2); - await expect(scenario.getSteps('failed')).toHaveCount(2); - await expect(scenario.getSteps('skipped')).toHaveCount(0); - await expect(scenario.getErrors()).toContainText([ - 'Expected: "foo" Received: "xxx"', - 'Expected: "bar" Received: "xxx"', - ]); - }); -}); - -test.describe('timeout', () => { - test.use({ featureUri: 'failing-step/timeout.feature' }); - - test('timeout in step', async ({ scenario }) => { - await expect(scenario.getSteps()).toContainText([ - 'Givenstep with page', // prettier-ignore - 'Giventimeouted step', - 'WhenAction 1', - 'screenshot', - // don't check 'Download trace' as it is attached to 'timeouted step' in pw 1.42 / 1.43 - // 'Download trace', - ]); - // don't check passed/skipped steps counts b/c in different PW versions it's different - await expect(scenario.getErrors()).toContainText([/Test timeout of \d+ms exceeded/]); - }); -}); diff --git a/test/reporter-cucumber-html/check-report/specs/failing-after-all.test.ts b/test/reporter-cucumber-html/features/failing-after-all/specs/todo.test.ts similarity index 95% rename from test/reporter-cucumber-html/check-report/specs/failing-after-all.test.ts rename to test/reporter-cucumber-html/features/failing-after-all/specs/todo.test.ts index 1fe0fedf..dbb8e698 100644 --- a/test/reporter-cucumber-html/check-report/specs/failing-after-all.test.ts +++ b/test/reporter-cucumber-html/features/failing-after-all/specs/todo.test.ts @@ -1,4 +1,4 @@ -import { test } from '../fixtures'; +// import { test } from '../../../check-report/fixtures'; // All these tests are skipped because they are marked as passed in Cucumber html report. // It's because afterAll hooks run in worker fixture teardown phase that is not related to any test. @@ -9,6 +9,7 @@ import { test } from '../fixtures'; // because it will be run several times. // Or just un-tagged afterAll hook + several files. +/* test.describe.skip('error in anonymous after all hook', () => { test.use({ featureUri: 'failing-after-all/anonymous.feature' }); @@ -33,3 +34,4 @@ test.describe.skip('timeout in after-all hook', () => { test('scenario 1', async () => {}); }); +*/ diff --git a/test/reporter-cucumber-html/features/failing-after/specs/error.feature.spec.ts b/test/reporter-cucumber-html/features/failing-after/specs/error.feature.spec.ts new file mode 100644 index 00000000..00eaf528 --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-after/specs/error.feature.spec.ts @@ -0,0 +1,84 @@ +import { normalize } from 'node:path'; +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +import { getPackageVersion } from '../../../../../src/utils'; + +// Automatic screenshot for failing fixtures teardown depends on pw version. +// see: https://github.com/microsoft/playwright/issues/29325 +const pwVersion = getPackageVersion('@playwright/test'); +export const hasScreenshotAfterHookError = pwVersion >= '1.42.0'; + +test('error in anonymous after hook', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText( + [ + 'Givenstep with page', + `Hook "AfterEach hook" failed: ${normalize('features/failing-after/steps.ts')}:`, // prettier-ignore + hasScreenshotAfterHookError ? 'screenshotDownload trace' : '', + ].filter(Boolean), + ); + await expect(scenario.getSteps('passed')).toHaveCount(1); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getTags()).toContainText(['@failing-anonymous-after-hook']); + await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); +}); + +test('error in named after hook', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText( + [ + 'Givenstep with page', + `Hook "failing named after hook" failed: ${normalize('features/failing-after/steps.ts')}:`, + hasScreenshotAfterHookError ? 'screenshotDownload trace' : '', + ].filter(Boolean), + ); + await expect(scenario.getSteps('passed')).toHaveCount(1); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getTags()).toContainText(['@failing-named-after-hook']); + await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); +}); + +test('error in fixture teardown (no step)', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'my attachment|before use', + 'Givenstep with page', + 'Givenstep that uses fixtureWithErrorInTeardown', + 'WhenAction 1', + `Hook "fixture: fixtureWithErrorInTeardown" failed: ${normalize('features/failing-after/fixtures.ts')}:`, + ]); + if (hasScreenshotAfterHookError) { + // position of screenshot item can vary + await expect(scenario.getSteps()).toContainText(['screenshot']); + } + await expect(scenario.getAttachments()).toContainText([ + 'my attachment|before use', // prettier-ignore + 'my attachment|after use', + ]); + await expect(scenario.getSteps('passed')).toHaveCount(3); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(0); + await expect(scenario.getErrors()).toContainText(['error in fixture teardown']); +}); + +test('error in fixture teardown (with step)', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'my attachment|outside step (before use)', + 'Givenstep with page', + 'Givenstep that uses fixtureWithErrorInTeardownStep', + 'WhenAction 1', + `Hook "step inside fixture" failed: ${normalize('features/failing-after/fixtures.ts')}:`, + 'my attachment|outside step (after use)', + ]); + if (hasScreenshotAfterHookError) { + // position of screenshot item can vary + await expect(scenario.getSteps()).toContainText(['screenshot']); + } + await expect(scenario.getAttachments()).toContainText([ + 'my attachment|outside step (before use)', + 'my attachment|in step', + 'my attachment|outside step (after use)', + ]); + await expect(scenario.getSteps('passed')).toHaveCount(3); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(0); + await expect(scenario.getErrors()).toContainText(['error in fixture teardown']); +}); diff --git a/test/reporter-cucumber-html/features/failing-after/specs/timeout.feature.spec.ts b/test/reporter-cucumber-html/features/failing-after/specs/timeout.feature.spec.ts new file mode 100644 index 00000000..8d9a44dd --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-after/specs/timeout.feature.spec.ts @@ -0,0 +1,51 @@ +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('timeout in fixture teardown', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'GivenAction 0', + 'Givenstep that uses fixtureWithTimeoutInTeardown', + 'WhenAction 1', + /Hook "(After Hooks|fixture: fixtureWithTimeoutInTeardown)" failed/, + ]); + // don't check screenshot as it's not reliable in timeouts + await expect(scenario.getSteps('passed')).toHaveCount(3); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getErrors()).toContainText([ + /but tearing down "fixtureWithTimeoutInTeardown" ran out of time|Tearing down "fixtureWithTimeoutInTeardown" exceeded the test timeout/, + ]); +}); + +test('timeout in step and in fixture teardown', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'GivenAction 0', + 'Giventimeouted step', + 'WhenAction 1', + 'Givenstep that uses fixtureWithTimeoutInTeardown', + /Hook "(After Hooks|fixture: fixtureWithTimeoutInTeardown)" failed/, + ]); + await expect(scenario.getSteps('passed')).toHaveCount(4); + // Don't check exact failed steps, it depends on PW version. + // - In modern PW versions (e.g. 1.49) timeouted fixture has 'error' field, we can find it. + // It shows: + // (1) Hook "fixture: fixtureWithTimeoutInTeardown" failed + // (2) Hook "After Hooks" failed: Test timeout of 1500ms exceeded + // - In older PW versions there is no 'error' field in timeouted fixture, we can't find it. + // It shows only fallback: + // (1) Hook "After Hooks" failed: Test timeout of 1500ms exceeded + await expect(scenario.getErrors()).toContainText([ + /Test timeout of \d+ms exceeded|Tearing down "fixtureWithTimeoutInTeardown" exceeded the test timeout/, + ]); +}); + +test('timeout in after hook', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'Givenstep with page', + // sometimes we still have "After Hooks" here, + // when duration = -1 is not in after hooks, and we cant detect which fixture timed out + /Hook "(my timeouted hook|After Hooks|fixture: \$afterEach)" failed/, + ]); + await expect(scenario.getErrors()).toContainText([ + /but tearing down "\$afterEach" ran out of time|Tearing down "\$afterEach" exceeded the test timeout/, + ]); +}); diff --git a/test/reporter-cucumber-html/features/failing-before-all/specs/anonymous.feature.spec.ts b/test/reporter-cucumber-html/features/failing-before-all/specs/anonymous.feature.spec.ts new file mode 100644 index 00000000..fcdf3fff --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before-all/specs/anonymous.feature.spec.ts @@ -0,0 +1,24 @@ +import { normalize } from 'node:path'; +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; +import { hasDownloadTrace } from './helpers'; + +test('scenario 1', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText( + [ + `Hook "BeforeAll hook" failed: ${normalize('features/failing-before-all/steps.ts')}:`, // prettier-ignore + 'GivenAction 1', + hasDownloadTrace ? 'Download trace' : '', + ].filter(Boolean), + ); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(1); + await expect(scenario.getErrors()).toContainText([`expect(true).toEqual(false)`]); +}); + +test('scenario 2', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'GivenAction 2', // prettier-ignore + ]); + await expect(scenario.getSteps('skipped')).toHaveCount(1); +}); diff --git a/test/reporter-cucumber-html/features/failing-before-all/specs/fixture.feature.spec.ts b/test/reporter-cucumber-html/features/failing-before-all/specs/fixture.feature.spec.ts new file mode 100644 index 00000000..e9b33975 --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before-all/specs/fixture.feature.spec.ts @@ -0,0 +1,15 @@ +import { normalize } from 'node:path'; +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('scenario 1', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + `Hook "fixture: workerFixtureWithErrorInSetup" failed: ${normalize('features/failing-before-all/fixtures.ts')}:`, + 'GivenAction 1', + 'Givenstep that uses workerFixtureWithErrorInSetup', + 'Download trace', + ]); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(2); + await expect(scenario.getErrors()).toContainText(['error in worker fixture setup']); +}); diff --git a/test/reporter-cucumber-html/features/failing-before-all/specs/helpers.ts b/test/reporter-cucumber-html/features/failing-before-all/specs/helpers.ts new file mode 100644 index 00000000..eb93e21a --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before-all/specs/helpers.ts @@ -0,0 +1,5 @@ +import { getPackageVersion } from '../../../../../src/utils'; + +// 'Download trace' appears in the report in case of error in before all hook since 1.42 +const pwVersion = getPackageVersion('@playwright/test'); +export const hasDownloadTrace = pwVersion >= '1.42.0'; diff --git a/test/reporter-cucumber-html/features/failing-before-all/specs/named.feature.spec.ts b/test/reporter-cucumber-html/features/failing-before-all/specs/named.feature.spec.ts new file mode 100644 index 00000000..e64e9706 --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before-all/specs/named.feature.spec.ts @@ -0,0 +1,17 @@ +import { normalize } from 'node:path'; +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; +import { hasDownloadTrace } from './helpers'; + +test('scenario 1', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText( + [ + `Hook "my hook" failed: ${normalize('features/failing-before-all/steps.ts')}:`, // prettier-ignore + 'GivenAction 1', + hasDownloadTrace ? 'Download trace' : '', + ].filter(Boolean), + ); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(1); + await expect(scenario.getErrors()).toContainText([`expect(true).toEqual(false)`]); +}); diff --git a/test/reporter-cucumber-html/features/failing-before-all/specs/timeout.feature.spec.ts b/test/reporter-cucumber-html/features/failing-before-all/specs/timeout.feature.spec.ts new file mode 100644 index 00000000..453dde4b --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before-all/specs/timeout.feature.spec.ts @@ -0,0 +1,14 @@ +import { normalize } from 'node:path'; +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('scenario 1', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + `Hook "my timeouted hook" failed: ${normalize('features/failing-before-all/steps.ts')}:`, // prettier-ignore + 'GivenAction 1', + ]); + await expect(scenario.getErrors()).toContainText([ + // here can be different error messages + /("beforeAll" hook timeout of \d+ms exceeded)|(browser has been closed)|(Browser closed)/, + ]); +}); diff --git a/test/reporter-cucumber-html/features/failing-before/specs/error.feature.spec.ts b/test/reporter-cucumber-html/features/failing-before/specs/error.feature.spec.ts new file mode 100644 index 00000000..7f2802de --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before/specs/error.feature.spec.ts @@ -0,0 +1,61 @@ +import { normalize } from 'node:path'; +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('error in anonymous before hook', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + `Hook "BeforeEach hook" failed: ${normalize('features/failing-before/steps.ts')}:`, // prettier-ignore + 'GivenAction 1', + 'screenshotDownload trace', + ]); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(1); + await expect(scenario.getTags()).toContainText(['@failing-anonymous-before-hook']); + await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); +}); + +test('error in named before hook', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + `Hook "failing named before hook" failed: ${normalize('features/failing-before/steps.ts')}:`, + 'GivenAction 1', + 'screenshotDownload trace', + ]); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(1); + await expect(scenario.getTags()).toContainText(['@failing-named-before-hook']); + await expect(scenario.getErrors()).toContainText([`expect(page).toHaveTitle`]); +}); + +test('error in fixture setup (no step)', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + `Hook "fixture: fixtureWithErrorInSetup" failed: ${normalize('features/failing-before/fixtures.ts')}:`, + 'Givenstep that uses fixtureWithErrorInSetup', + 'WhenAction 1', + 'screenshot', + ]); + await expect(scenario.getAttachments()).toHaveText([ + 'my attachment|outside step', + 'my attachment|in step', + 'screenshot', + ]); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(2); + await expect(scenario.getErrors()).toContainText(['error in fixture setup']); +}); + +test('error in fixture setup (with step)', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + `Hook "step inside fixture" failed: ${normalize('features/failing-before/fixtures.ts')}:`, + 'Givenstep that uses fixtureWithErrorInSetupStep', + 'WhenAction 2', + 'screenshot', + ]); + await expect(scenario.getAttachments()).toHaveText([ + 'my attachment|in step', + 'my attachment|outside step', + 'screenshot', + ]); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(2); + await expect(scenario.getErrors()).toContainText(['error in fixture setup']); +}); diff --git a/test/reporter-cucumber-html/features/failing-before/specs/timeout.feature.spec.ts b/test/reporter-cucumber-html/features/failing-before/specs/timeout.feature.spec.ts new file mode 100644 index 00000000..aa9e4825 --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-before/specs/timeout.feature.spec.ts @@ -0,0 +1,37 @@ +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('timeout in fixture setup', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'GivenAction 0', + 'Givenstep that uses fixtureWithTimeoutInSetup', + 'WhenAction 1', + ]); + // 1. position of error message sometimes appears in After Hooks, so check it separately + // 2. here can be different error messages + await expect(scenario.getSteps()).toContainText([/Hook "(.+)" failed/]); + // screenshot position changes between PW versions, so check it separately + await expect(scenario.getSteps()).toContainText(['screenshot']); + // sometimes error is the following: + // "browser.newContext: Target page, context or browser has been closed" + // in that case there are two errors in test report. + expect(await scenario.getSteps('failed').count()).toBeGreaterThan(0); + await expect(scenario.getSteps('skipped')).toHaveCount(3); + await expect(scenario.getErrors()).toContainText([ + // here can be different error messages + /(Test timeout of \d+ms exceeded while setting up "fixtureWithTimeoutInSetup")|(browser has been closed)|(Browser closed)|(Page closed)/, + ]); +}); + +test('timeout in before hook', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + // sometimes we still have 'BeforeEach Hooks|Before Hooks' here, not 'my timeouted hook', + // when 'duration' is not -1, we can't find timeouted item. + /Hook "(my timeouted hook|BeforeEach Hooks|Before Hooks|fixture: \$beforeEach)" failed:/, // prettier-ignore + 'GivenAction 1', + ]); + await expect(scenario.getErrors()).toContainText([ + // here can be different error messages + /(Test timeout of \d+ms exceeded while running "beforeEach" hook)|(browser has been closed)|(Browser closed)|(Page closed)/, + ]); +}); diff --git a/test/reporter-cucumber-html/check-report/specs/failing-bg.test.ts b/test/reporter-cucumber-html/features/failing-bg/specs/error.feature.spec.ts similarity index 91% rename from test/reporter-cucumber-html/check-report/specs/failing-bg.test.ts rename to test/reporter-cucumber-html/features/failing-bg/specs/error.feature.spec.ts index cd2ec229..60e775c3 100644 --- a/test/reporter-cucumber-html/check-report/specs/failing-bg.test.ts +++ b/test/reporter-cucumber-html/features/failing-bg/specs/error.feature.spec.ts @@ -1,7 +1,5 @@ import { expect } from '@playwright/test'; -import { test } from '../fixtures'; - -test.use({ featureUri: 'failing-bg/error.feature' }); +import { test } from '../../../check-report/fixtures'; test('background is failing', async ({ feature }) => { const background = feature.getBackground(); diff --git a/test/reporter-cucumber-html/features/failing-step/specs/error.feature.spec.ts b/test/reporter-cucumber-html/features/failing-step/specs/error.feature.spec.ts new file mode 100644 index 00000000..e5b81ef7 --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-step/specs/error.feature.spec.ts @@ -0,0 +1,50 @@ +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('error in step', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'Givenstep with page', // prettier-ignore + 'Givenfailing step', + 'WhenAction 1', + 'screenshotDownload trace', + ]); + await expect(scenario.getSteps('passed')).toHaveCount(1); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(1); + await expect(scenario.getErrors()).toContainText(['expect(true).toBe(false)']); +}); + +test('failing match snapshot', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'Whenstep with page', + 'Thenerror in match snapshot', + ]); + await expect(scenario.getAttachments()).toHaveText([ + 'error-in-step-failing-match-snapshot-1-expected.txtbla-bla', + 'error-in-step-failing-match-snapshot-1-actual.txtExample Domain', + 'screenshot', + ]); + await expect(scenario.getSteps('passed')).toHaveCount(1); + await expect(scenario.getSteps('failed')).toHaveCount(1); + await expect(scenario.getSteps('skipped')).toHaveCount(0); + await expect(scenario.getErrors()).toContainText(['toMatchSnapshot']); +}); + +test('soft assertions', async ({ scenario }) => { + await expect(scenario.getSteps()).toHaveText([ + /Givenfailing soft assertion "foo"/, + 'AndAction 1', + /Andfailing soft assertion "bar"/, + 'AndAction 2', + // not 'screenshot', b/c no page + // 'screenshot', + 'Download trace', + ]); + await expect(scenario.getSteps('passed')).toHaveCount(2); + await expect(scenario.getSteps('failed')).toHaveCount(2); + await expect(scenario.getSteps('skipped')).toHaveCount(0); + await expect(scenario.getErrors()).toContainText([ + 'Expected: "foo" Received: "xxx"', + 'Expected: "bar" Received: "xxx"', + ]); +}); diff --git a/test/reporter-cucumber-html/features/failing-step/specs/timeout.feature.spec.ts b/test/reporter-cucumber-html/features/failing-step/specs/timeout.feature.spec.ts new file mode 100644 index 00000000..30581643 --- /dev/null +++ b/test/reporter-cucumber-html/features/failing-step/specs/timeout.feature.spec.ts @@ -0,0 +1,15 @@ +import { expect } from '@playwright/test'; +import { test } from '../../../check-report/fixtures'; + +test('timeout in step', async ({ scenario }) => { + await expect(scenario.getSteps()).toContainText([ + 'Givenstep with page', // prettier-ignore + 'Giventimeouted step', + 'WhenAction 1', + 'screenshot', + // don't check 'Download trace' as it is attached to 'timeouted step' in pw 1.42 / 1.43 + // 'Download trace', + ]); + // don't check passed/skipped steps counts b/c in different PW versions it's different + await expect(scenario.getErrors()).toContainText([/Test timeout of \d+ms exceeded/]); +}); diff --git a/test/reporter-cucumber-html/check-report/specs/success.test.ts b/test/reporter-cucumber-html/features/success/specs/success.feature.spec.ts similarity index 96% rename from test/reporter-cucumber-html/check-report/specs/success.test.ts rename to test/reporter-cucumber-html/features/success/specs/success.feature.spec.ts index b4ecafd3..e0c11b5b 100644 --- a/test/reporter-cucumber-html/check-report/specs/success.test.ts +++ b/test/reporter-cucumber-html/features/success/specs/success.feature.spec.ts @@ -1,7 +1,5 @@ import { expect } from '@playwright/test'; -import { test } from '../fixtures'; - -test.use({ featureUri: 'success/sample.feature' }); +import { test } from '../../../check-report/fixtures'; test('Feature tags', async ({ feature }) => { await expect(feature.getTags()).toHaveText(['@feature-tag']); diff --git a/test/reporter-cucumber-html/features/success/sample.feature b/test/reporter-cucumber-html/features/success/success.feature similarity index 100% rename from test/reporter-cucumber-html/features/success/sample.feature rename to test/reporter-cucumber-html/features/success/success.feature diff --git a/test/reporter-cucumber-html/playwright.config.ts b/test/reporter-cucumber-html/playwright.config.ts index 1955855e..2810b735 100644 --- a/test/reporter-cucumber-html/playwright.config.ts +++ b/test/reporter-cucumber-html/playwright.config.ts @@ -4,6 +4,7 @@ import { testTimeout } from './timeout'; const testDir = defineBddConfig({ featuresRoot: './features', + steps: ['./features/**/*.ts', '!**/*.spec.ts'], }); export default defineConfig({ diff --git a/test/reporter-cucumber-merge/playwright.config.ts b/test/reporter-cucumber-merge/playwright.config.ts index 9a58d037..1ab18f62 100644 --- a/test/reporter-cucumber-merge/playwright.config.ts +++ b/test/reporter-cucumber-merge/playwright.config.ts @@ -4,6 +4,7 @@ import { testTimeout } from '../reporter-cucumber-html/timeout'; const testDir = defineBddConfig({ featuresRoot: './features', + steps: ['./features/**/*.ts', '!**/*.spec.ts'], }); const isShardRun = process.argv.some((a) => a.startsWith('--shard'));