Skip to content

Commit

Permalink
refactor: do not keep logic related to fail, fixme, only, `skip…
Browse files Browse the repository at this point in the history
…` and `slow` in `test` and `suite` decorators (#51)

* refactor: do not keep logic related to `fail`, `fixme`, `only`, `skip`, `slow` in `@test` and `@suite` decorators

* docs: add `only` parameter to `@test` and `@suite` docs

* chore: generate changeset
  • Loading branch information
SebastianSedzik committed Jan 11, 2024
1 parent 7d7e141 commit a83fc39
Show file tree
Hide file tree
Showing 9 changed files with 34 additions and 186 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-crews-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"playwright-decorators": patch
---

`@test` and `@suite` decorators do no longer keep logic related to `fail`, `only`, `skip`, `slow`. Code was moved to dedicated decorators.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class MyTestSuite {

#### Options
- `name` (optional) - name of the test suite. By default, name of the class.
- `only` (optional) - declares focused test suite. If there are some focused @test(s) or @suite(s), all of them will be run but nothing else.


### Creating a test: `@test(options?)`
Expand All @@ -93,6 +94,7 @@ class MyTestSuite {

#### Options
- `name` (optional) - name of the test. By default, name of the method.
- `only` (optional) - declares focused test. If there are some focused @test(s) or @suite(s), all of them will be run but nothing else.


### Run method before all tests in the suite: `@beforeAll()`
Expand Down
5 changes: 4 additions & 1 deletion lib/annotation.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createTestDecorator } from './custom'
import playwright from '@playwright/test'

interface AnnotationDecoratorOptions {
type: 'skip' | 'fail' | 'issue' | 'slow' | string
Expand All @@ -11,5 +12,7 @@ interface AnnotationDecoratorOptions {
*/
export const annotation = (options: AnnotationDecoratorOptions) =>
createTestDecorator('annotation', ({ test }) => {
test.annotations.push(options)
test.beforeTest(() => {
playwright.info().annotations.push(options)
})
})
5 changes: 3 additions & 2 deletions lib/fail.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSuiteAndTestDecorator } from './custom'
import playwright from '@playwright/test'

/**
* Marks a @test or @suite as "should fail".
Expand All @@ -9,9 +10,9 @@ export const fail = (reason?: string) =>
createSuiteAndTestDecorator(
'fail',
({ suite }) => {
suite.fail = reason || true
suite.initialized(() => playwright.fail(true, reason))
},
({ test }) => {
test.fail = reason || true
test.beforeTest(() => playwright.fail(true, reason))
}
)
5 changes: 3 additions & 2 deletions lib/fixme.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSuiteAndTestDecorator } from './custom'
import playwright from '@playwright/test'

/**
* Marks a @test or @suite as "fixme", with the intention to fix (with optional reason).
Expand All @@ -8,9 +9,9 @@ export const fixme = (reason?: string) =>
createSuiteAndTestDecorator(
'fixme',
({ suite }) => {
suite.fixme = reason || true
suite.initialized(() => playwright.fixme(true, reason))
},
({ test }) => {
test.fixme = reason || true
test.beforeTest(() => playwright.fixme(true, reason))
}
)
5 changes: 3 additions & 2 deletions lib/skip.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSuiteAndTestDecorator } from './custom'
import playwright from '@playwright/test'

/**
* Skip @test or @suite (with optional reason).
Expand All @@ -7,9 +8,9 @@ export const skip = (reason?: string) =>
createSuiteAndTestDecorator(
'skip',
({ suite }) => {
suite.skip = reason || true
suite.initialized(() => playwright.skip(true, reason))
},
({ test }) => {
test.skip = reason || true
test.beforeTest(() => playwright.skip(true, reason))
}
)
5 changes: 3 additions & 2 deletions lib/slow.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createSuiteAndTestDecorator } from './custom'
import playwright from '@playwright/test'

/**
* Marks a @test or @suite as "slow" (with optional reason).
Expand All @@ -8,9 +9,9 @@ export const slow = (reason?: string) =>
createSuiteAndTestDecorator(
'slow',
({ suite }) => {
suite.slow = reason || true
suite.initialized(() => playwright.slow(true, reason))
},
({ test }) => {
test.slow = reason || true
test.beforeTest(() => playwright.slow(true, reason))
}
)
85 changes: 4 additions & 81 deletions lib/suite.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,6 @@ interface SuiteDecoratorOptions {
* Name of the suite. Default: name of the suite class
*/
name?: string
/**
* Skip suite (with optional reason)
*/
skip?: string | boolean
/**
* Mark suite as "slow" (with optional reason).
* Slow test will be given triple the default timeout.
*/
slow?: string | boolean
/**
* 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
/**
* Declares a focused suite.
* If there are some focused @test(s) or @suite(s), all of them will be run but nothing else.
Expand All @@ -37,10 +17,6 @@ interface SuiteDecoratorOptions {

export class SuiteDecorator implements SuiteDecoratorOptions {
name: string
skip: string | boolean = false
slow: string | boolean = false
fail: string | boolean = false
fixme: string | boolean = false
only = false

private initializedHooks: SuiteHook[] = []
Expand All @@ -54,63 +30,11 @@ export class SuiteDecorator implements SuiteDecoratorOptions {
Object.assign(this, options)
}

private handleSkip() {
if (this.skip === false) {
return
}

if (typeof this.skip === 'string') {
return playwright.skip(true, this.skip)
}

playwright.skip()
}

private handleSlow() {
if (this.slow === false) {
return
}

if (typeof this.slow === 'string') {
return playwright.slow(true, this.slow)
}

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
}

if (typeof this.fixme === 'string') {
return playwright.fixme(true, this.fixme)
}

return playwright.fixme()
}

private handleInitializedHooks() {
return Promise.all(this.initializedHooks.map((hookFn) => hookFn()))
}

private async runSuite(userSuiteCode: () => Promise<void>) {
this.handleSkip()
this.handleSlow()
this.handleFail()
this.handleFixme()
this.handleInitializedHooks()

return userSuiteCode()
Expand All @@ -120,15 +44,14 @@ export class SuiteDecorator implements SuiteDecoratorOptions {
* Run playwright.describe function using all collected data.
*/
run() {
const playwrightRunSuite = this.only ? playwright.describe.only : playwright.describe
const suiteRunner = this.only ? playwright.describe.only : playwright.describe

playwrightRunSuite(this.name, () => {
return this.runSuite(() => new this.suiteClass())
})
suiteRunner(this.name, () => this.runSuite(() => new this.suiteClass()))
}

/**
* Declares an `initialized` hook that is executed after suite is initialized.
* Declares an `initialized` hook that is executed in `playwright.describe` context, when suite is executed.
* It is equivalent to code: playwright.describe(..., () => { initialized(); ... })
*/
initialized(hookFn: SuiteHook) {
this.initializedHooks.push(hookFn)
Expand Down
103 changes: 7 additions & 96 deletions lib/test.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,16 @@ interface TestDecoratorOptions {
* Name of the test. Default: name of the method
*/
name?: string
/**
* Skip suite (with optional reason)
*/
skip?: string | boolean
/**
* Mark test as "slow" (with optional reason).
* Slow test will be given triple the default timeout.
*/
slow?: string | boolean
/**
* Marks a test 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 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.
*/
only?: boolean
/**
* Add custom annotation to a test.
* Annotations are accessible via test.info().annotations. Many reporters show annotations, for example 'html'.
*/
annotations?: { type: string; description?: string }[]
}

export class TestDecorator implements TestDecoratorOptions {
name: string
skip: string | boolean = false
slow: string | boolean = false
fail: string | boolean = false
fixme: string | boolean = false
only = false
annotations: { type: string; description?: string }[] = []

private beforeTestHooks: TestHook[] = []
private afterTestHooks: TestHook[] = []
Expand All @@ -62,60 +32,6 @@ export class TestDecorator implements TestDecoratorOptions {
Object.assign(this, options)
}

private handleSkip() {
if (this.skip === false) {
return
}

if (typeof this.skip === 'string') {
return playwright.skip(true, this.skip)
}

playwright.skip()
}

private handleSlow() {
if (this.slow === false) {
return
}

if (typeof this.slow === 'string') {
return playwright.slow(true, this.slow)
}

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
}

if (typeof this.fixme === 'string') {
return playwright.fixme(true, this.fixme)
}

return playwright.fixme()
}

private handleAnnotations() {
this.annotations.forEach((annotation) => {
playwright.info().annotations.push(annotation)
})
}

private handleBeforeTestHooks() {
return Promise.all(this.beforeTestHooks.map((initializer) => initializer()))
}
Expand All @@ -128,14 +44,9 @@ export class TestDecorator implements TestDecoratorOptions {
* Run playwright.test function using all collected data.
*/
run(executionContext: ClassMethodDecoratorContext) {
const decoratedTest: TestDecoratorFunction =
const extendedTestMethod: TestDecoratorFunction =
(testFunction) =>
async (...args) => {
this.handleAnnotations()
this.handleSkip()
this.handleSlow()
this.handleFail()
this.handleFixme()
await this.handleBeforeTestHooks()

// set correct executionContext (test class)
Expand All @@ -144,22 +55,22 @@ export class TestDecorator implements TestDecoratorOptions {
await this.handleAfterTestHooks()
}

const decoratedTestMethod = decoratePlaywrightTest(this.testMethod, decoratedTest)

const playwrightRunTest = this.only ? playwright.only : playwright
const testRunner = this.only ? playwright.only : playwright

playwrightRunTest(this.name, decoratedTestMethod)
testRunner(this.name, decoratePlaywrightTest(this.testMethod, extendedTestMethod))
}

/**
* Declares an `before` hook that is executed before test.
* Declares an `before` hook that is executed in `playwright()` context just before execution of test code.
* It is equivalent to code: playwright(..., () => { beforeTest(); ... })
*/
beforeTest(initializer: TestHook) {
this.beforeTestHooks.push(initializer)
}

/**
* Declares an `after` hook that is executed after test.
* Declares an `after` hook that is executed in `playwright()` context just after execution of test code.
* It is equivalent to code: playwright(..., () => { ...; afterTest() })
*/
afterTest(initializer: TestHook) {
this.afterTestHooks.push(initializer)
Expand Down

0 comments on commit a83fc39

Please sign in to comment.