From 153f2a4ffe0ce179b511334de49a0dbee49f5a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20S=C4=99dzik?= Date: Sat, 22 Jul 2023 11:32:33 +0200 Subject: [PATCH 1/2] feat: add `@fixme` decorator --- README.md | 28 ++++++++++++++++++++++ lib/fixme.decorator.ts | 18 ++++++++++++++ lib/index.ts | 1 + lib/suite.decorator.ts | 19 +++++++++++++++ lib/test.decorator.ts | 19 +++++++++++++++ tests/fixme.spec.ts | 53 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+) create mode 100644 lib/fixme.decorator.ts create mode 100644 tests/fixme.spec.ts diff --git a/README.md b/README.md index 54f1a55..268513d 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,34 @@ class MyTestSuite { - `reason` (optional) - reason of marking as "slow". Will be displayed in the test report. +### Mark test or suite as "fixme", with the intention to fix it: `@fixme(reason?: string)` +Marks a `@test` or `@suite` as "fixme", with the intention to fix (with optional reason). +Decorated tests or suites will not be run. + +```ts +import { suite, test, fixme } from 'playwright-decorators'; + +// Mark test suite as "fixme" +@fixme() // <-- Decorate suite with @fixme() +@suite() +class FixmeTestSuite { +} + +// Or mark selected test as "fixme" +@suite() +class MyTestSuite { + @fixme() // <-- Decorate test with @fixme() + @test() + async fixmeTest({ page }) { + // ... + } +} +``` + +#### Options +- `reason` (optional) - reason of marking as "fixme". Will be displayed in the test report. + + ### Run only selected test(s) or suite(s): `@only()` Declares a focused `@test` or `@suite`. If there are some focused tests or suites, all of them will be run but nothing else. diff --git a/lib/fixme.decorator.ts b/lib/fixme.decorator.ts new file mode 100644 index 0000000..93e849d --- /dev/null +++ b/lib/fixme.decorator.ts @@ -0,0 +1,18 @@ +import {SuiteDecoratedMethod} from "./suite.decorator"; +import {TestDecoratedMethod} from "./test.decorator"; + +/** + * Marks a @test or @suite as "fixme", with the intention to fix (with optional reason). + * Decorated tests or suites will not be run. + */ +export const fixme = (reason?: string) => function(originalMethod: any, context?: any) { + if ((originalMethod as SuiteDecoratedMethod)?.suiteDecorator) { + originalMethod.suiteDecorator.fixme = reason || true; + return; + } + + if ((originalMethod as TestDecoratedMethod)?.testDecorator) { + originalMethod.testDecorator.fixme = reason || true; + return; + } +} diff --git a/lib/index.ts b/lib/index.ts index 874de43..86fa54a 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -10,6 +10,7 @@ export { afterEach } from './afterEach.decorator'; export { skip } from './skip.decorator'; export { slow } from './slow.decorator'; export { fail } from './fail.decorator'; +export { fixme } from './fixme.decorator'; export { only } from './only.decorator'; // helpers export { tag } from './tag.decorator'; diff --git a/lib/suite.decorator.ts b/lib/suite.decorator.ts index c937705..91c7934 100644 --- a/lib/suite.decorator.ts +++ b/lib/suite.decorator.ts @@ -16,6 +16,11 @@ interface SuiteDecoratorOptions { * Slow test will be given triple the default timeout. */ slow?: string | boolean; + /** + * Marks a @test or @suite as "fixme", with the intention to fix (with optional reason). + * Decorated tests or suites will not be run. + */ + fixme?: string | boolean; /** * Declares a focused suite. * If there are some focused @test(s) or @suite(s), all of them will be run but nothing else. @@ -27,6 +32,7 @@ class SuiteDecorator implements SuiteDecoratorOptions { name: string; skip: string | boolean = false; slow: string | boolean = false; + fixme: string | boolean = false; only = false; constructor(private suiteClass: Constructor, options: SuiteDecoratorOptions) { @@ -58,10 +64,23 @@ class SuiteDecorator implements SuiteDecoratorOptions { return playwright.slow(); } + + private handleFixme() { + if (this.fixme === false) { + return; + } + + if (typeof this.fixme === 'string') { + return playwright.fixme(true, this.fixme); + } + + return playwright.fixme(); + } private runSuite(userSuiteCode: () => Promise) { this.handleSkip(); this.handleSlow(); + this.handleFixme(); return userSuiteCode(); } diff --git a/lib/test.decorator.ts b/lib/test.decorator.ts index 5b6d464..8ee7cda 100644 --- a/lib/test.decorator.ts +++ b/lib/test.decorator.ts @@ -21,6 +21,11 @@ interface TestDecoratorOptions { * This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixe */ fail?: string | boolean + /** + * Marks a test as "fixme", with the intention to fix (with optional reason). + * Decorated test will not be run. + */ + fixme?: string | boolean; /** * Declares a focused test. * If there are some focused @test(s) or @suite(s), all of them will be run but nothing else. @@ -33,6 +38,7 @@ class TestDecorator implements TestDecoratorOptions { skip: string | boolean = false; slow: string | boolean = false; fail: string | boolean = false; + fixme: string | boolean = false; only = false; constructor(private testMethod: any, options: TestDecoratorOptions) { @@ -77,6 +83,18 @@ class TestDecorator implements TestDecoratorOptions { return playwright.fail(); } + private handleFixme() { + if (this.fixme === false) { + return; + } + + if (typeof this.fixme === 'string') { + return playwright.fixme(true, this.fixme); + } + + return playwright.fixme(); + } + /** * Run playwright.test function using all collected data. */ @@ -85,6 +103,7 @@ class TestDecorator implements TestDecoratorOptions { this.handleSkip(); this.handleSlow(); this.handleFail(); + this.handleFixme(); // set correct executionContext (test class) return testFunction.call(executionContext, ...args); diff --git a/tests/fixme.spec.ts b/tests/fixme.spec.ts new file mode 100644 index 0000000..e29ff06 --- /dev/null +++ b/tests/fixme.spec.ts @@ -0,0 +1,53 @@ +import playwright, {expect} from "@playwright/test"; +import {suite, test, slow, beforeAll, skip, fixme} from "../lib"; + +playwright.describe('@fixme decorator', () => { + playwright.describe('with @suite', () => { + const called: string[] = []; + + @fixme() + @suite() + class SkipSuite { + @test() + async test() { + called.push('test'); + } + + @test() + async test2() { + called.push('test2'); + } + } + + playwright('@fixme decorator should not call any @test methods from suite', () => { + expect(called).toEqual([]); + }); + }); + + playwright.describe('with @test', () => { + const called: string[] = []; + + @suite() + class TestSuite { + @test() + async test() { + called.push('test'); + } + + @fixme() + @test() + async fixmeTest() { + called.push('fixmeTest'); + } + + @test() + async test2() { + called.push('test2'); + } + } + + playwright('@fixme decorator should not execute @test method', () => { + expect(called).not.toContain('fixmeTest'); + }) + }); +}); From 8a4598adb3b073cc9b956746c46b6b615ba74a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20S=C4=99dzik?= Date: Sat, 22 Jul 2023 11:41:09 +0200 Subject: [PATCH 2/2] feat: support `@fail` in `@suite` --- README.md | 55 ++++++++++++++++++++++++------------------ lib/fail.decorator.ts | 8 +++++- lib/suite.decorator.ts | 24 ++++++++++++++++-- tests/fail.spec.ts | 26 +++++++++++++++++++- 4 files changed, 85 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 268513d..536e04f 100644 --- a/README.md +++ b/README.md @@ -152,14 +152,21 @@ class MyTestSuite { - `reason` (optional) - reason of skipping. Will be displayed in the test report. -### Mark test as "should fail": `@fail(reason?: string)` -Mark single `@test` as "should fail". +### Mark test or suite as "should fail": `@fail(reason?: string)` +Mark single `@test` or `@suite` as "should fail". Playwright Test runs this test and ensures that it is actually failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed. ```ts import { suite, test, fail } from 'playwright-decorators'; +// Mark suite as "fail", ensure that all tests from suite fail +@fail() // <-- Decorate suite with @fail() +@suite() +class FailTestSuite { +} + +// Or mark selected test as "fail" @suite() class MyTestSuite { @fail() // <-- Decorate test with @fail() @@ -174,60 +181,60 @@ class MyTestSuite { - `reason` (optional) - reason of marking as "should fail". Will be displayed in the test report. -### Mark test or suite as "slow": `@slow(reason?: string)` -Mark single `@test` or `@suite` as "slow". -Slow test will be given triple the default timeout. +### Mark test or suite as "fixme", with the intention to fix it: `@fixme(reason?: string)` +Marks a `@test` or `@suite` as "fixme", with the intention to fix (with optional reason). +Decorated tests or suites will not be run. ```ts -import { suite, test, skip } from 'playwright-decorators'; +import { suite, test, fixme } from 'playwright-decorators'; -// Mark test suite as "slow" -@slow() // <-- Decorate suite with @slow() +// Mark test suite as "fixme" +@fixme() // <-- Decorate suite with @fixme() @suite() -class SlowTestSuite { +class FixmeTestSuite { } -// Or mark selected test as "slow" +// Or mark selected test as "fixme" @suite() class MyTestSuite { - @slow() // <-- Decorate test with @slow() + @fixme() // <-- Decorate test with @fixme() @test() - async slowTest({ page }) { + async fixmeTest({ page }) { // ... } } ``` #### Options -- `reason` (optional) - reason of marking as "slow". Will be displayed in the test report. +- `reason` (optional) - reason of marking as "fixme". Will be displayed in the test report. -### Mark test or suite as "fixme", with the intention to fix it: `@fixme(reason?: string)` -Marks a `@test` or `@suite` as "fixme", with the intention to fix (with optional reason). -Decorated tests or suites will not be run. +### Mark test or suite as "slow": `@slow(reason?: string)` +Mark single `@test` or `@suite` as "slow". +Slow test will be given triple the default timeout. ```ts -import { suite, test, fixme } from 'playwright-decorators'; +import { suite, test, skip } from 'playwright-decorators'; -// Mark test suite as "fixme" -@fixme() // <-- Decorate suite with @fixme() +// Mark test suite as "slow" +@slow() // <-- Decorate suite with @slow() @suite() -class FixmeTestSuite { +class SlowTestSuite { } -// Or mark selected test as "fixme" +// Or mark selected test as "slow" @suite() class MyTestSuite { - @fixme() // <-- Decorate test with @fixme() + @slow() // <-- Decorate test with @slow() @test() - async fixmeTest({ page }) { + async slowTest({ page }) { // ... } } ``` #### Options -- `reason` (optional) - reason of marking as "fixme". Will be displayed in the test report. +- `reason` (optional) - reason of marking as "slow". Will be displayed in the test report. ### Run only selected test(s) or suite(s): `@only()` diff --git a/lib/fail.decorator.ts b/lib/fail.decorator.ts index 28aa38e..0787c2a 100644 --- a/lib/fail.decorator.ts +++ b/lib/fail.decorator.ts @@ -1,11 +1,17 @@ +import {SuiteDecoratedMethod} from "./suite.decorator"; import {TestDecoratedMethod} from "./test.decorator"; /** - * Marks a @test as "should fail". + * Marks a @test or @suite as "should fail". * Playwright Test runs this test and ensures that it is actually failing. * This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed. */ export const fail = (reason?: string) => function(originalMethod: any, context?: any) { + if ((originalMethod as SuiteDecoratedMethod)?.suiteDecorator) { + originalMethod.suiteDecorator.fail = reason || true; + return; + } + if ((originalMethod as TestDecoratedMethod)?.testDecorator) { originalMethod.testDecorator.fail = reason || true; return; diff --git a/lib/suite.decorator.ts b/lib/suite.decorator.ts index 91c7934..3316634 100644 --- a/lib/suite.decorator.ts +++ b/lib/suite.decorator.ts @@ -17,8 +17,14 @@ interface SuiteDecoratorOptions { */ slow?: string | boolean; /** - * Marks a @test or @suite as "fixme", with the intention to fix (with optional reason). - * Decorated tests or suites will not be run. + * Marks a suite as "should fail". + * Playwright Test runs all test from suite and ensures that they are actually failing. + * This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixe + */ + fail?: string | boolean + /** + * Marks a suite as "fixme", with the intention to fix (with optional reason). + * Decorated suite will not be run. */ fixme?: string | boolean; /** @@ -32,6 +38,7 @@ class SuiteDecorator implements SuiteDecoratorOptions { name: string; skip: string | boolean = false; slow: string | boolean = false; + fail: string | boolean = false; fixme: string | boolean = false; only = false; @@ -65,6 +72,18 @@ class SuiteDecorator implements SuiteDecoratorOptions { return playwright.slow(); } + private handleFail() { + if (this.fail === false) { + return; + } + + if (typeof this.fail === 'string') { + return playwright.fail(true, this.fail); + } + + return playwright.fail(); + } + private handleFixme() { if (this.fixme === false) { return; @@ -80,6 +99,7 @@ class SuiteDecorator implements SuiteDecoratorOptions { private runSuite(userSuiteCode: () => Promise) { this.handleSkip(); this.handleSlow(); + this.handleFail(); this.handleFixme(); return userSuiteCode(); diff --git a/tests/fail.spec.ts b/tests/fail.spec.ts index 9b3396c..3ccb847 100644 --- a/tests/fail.spec.ts +++ b/tests/fail.spec.ts @@ -2,6 +2,30 @@ import playwright, {expect} from "@playwright/test"; import {suite, test, fail} from "../lib"; playwright.describe('@fail decorator', () => { + playwright.describe('with @suite', () => { + const called: string[] = []; + + @fail() + @suite() + class SkipSuite { + @test() + async test() { + called.push('test'); + expect(true).toBe(false); + } + + @test() + async test2() { + called.push('test2'); + expect(true).toBe(false); + } + } + + playwright('@fail decorator should not throw error when tests intentionally fails', () => { + expect(called).toEqual(['test', 'test2']); + }); + }); + playwright.describe('with @test', () => { const called: string[] = []; @@ -15,7 +39,7 @@ playwright.describe('@fail decorator', () => { } } - playwright('@fail decorator should not throw error when test fails', () => { + playwright('@fail decorator should not throw error when test intentionally fails', () => { // is called and not throw error expect(called).toEqual(['failingTest']); });