Skip to content

Commit

Permalink
feat: add @only decorator (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianSedzik committed Jul 16, 2023
1 parent 6359e65 commit 539ba8e
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 21 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
node-version: '18.x'
cache: 'npm'
- run: npm ci
- run: npx playwright install
- run: npm run build
- run: npx playwright install chromium
- run: npm run lint
- run: npm run build
- run: npm test
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,27 @@ class MyTestSuite {

#### Options
- `reason` (optional) - reason of marking as "slow". 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.

```ts
import { suite, test, only } from 'playwright-decorators';

// Run only selected test suite(s)
@only() // <-- Decorate suite with @only()
@suite()
class FocusedTestSuite {
}

// Or run only selected test(s)
@suite()
class TestSuite {
@only() // <-- Decorate test with @only()
@test()
async focusedTest({ page }) {
// ...
}
}
```
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { afterEach } from './afterEach.decorator';
// annotations
export { skip } from './skip.decorator';
export { slow } from './slow.decorator';
export { only } from './only.decorator';
18 changes: 18 additions & 0 deletions lib/only.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {SuiteDecoratedMethod} from "./suite.decorator";
import {TestDecoratedMethod} from "./test.decorator";

/**
* Declares a focused test.
* If there are some focused @test(s) or @suite(s), all of them will be run but nothing else.
*/
export const only = () => function(originalMethod: any, context?: any) {
if ((originalMethod as SuiteDecoratedMethod)?.suiteDecorator) {
originalMethod.suiteDecorator.only = true;
return;
}

if ((originalMethod as TestDecoratedMethod)?.testDecorator) {
originalMethod.testDecorator.only = true;
return;
}
}
10 changes: 9 additions & 1 deletion lib/suite.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ interface SuiteDecoratorOptions {
* Slow test will be given triple the default timeout.
*/
slow?: 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.
*/
only?: boolean;
}

class SuiteDecorator implements SuiteDecoratorOptions {
name: string;
skip: string | boolean = false;
slow: string | boolean = false;
only = false;

constructor(private suiteClass: Constructor, options: SuiteDecoratorOptions) {
this.name = suiteClass.name;
Expand Down Expand Up @@ -64,7 +70,9 @@ class SuiteDecorator implements SuiteDecoratorOptions {
* Run playwright.describe function using all collected data.
*/
run() {
playwright.describe(this.name, () => {
const playwrightRunSuite = this.only ? playwright.describe.only : playwright.describe;

playwrightRunSuite(this.name, () => {
return this.runSuite(() => new this.suiteClass())
});
}
Expand Down
10 changes: 9 additions & 1 deletion lib/test.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ interface TestDecoratorOptions {
* Slow test will be given triple the default timeout.
*/
slow?: 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;
}

class TestDecorator implements TestDecoratorOptions {
name: string;
skip: string | boolean = false;
slow: string | boolean = false;
only = false;

constructor(private testMethod: any, options: TestDecoratorOptions) {
this.name = testMethod.name;
Expand Down Expand Up @@ -69,7 +75,9 @@ class TestDecorator implements TestDecoratorOptions {
decoratedTest
);

playwright(this.name, decoratedTestMethod);
const playwrightRunTest = this.only ? playwright.only : playwright;

playwrightRunTest(this.name, decoratedTestMethod);
}
}

Expand Down
17 changes: 17 additions & 0 deletions tests/__mocks__/mockFn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Mock object method.
* Returns cleanup function to restore original method.
* @param target - target object.
* @param targetProperty - name of property from target object.
* @param mock - mock function to be called instead of original one.
*/
export const mockFn = <T>(target: T, targetProperty: keyof T, mock: (...args: any[]) => void): () => void => {
const originalFn = target[targetProperty];

// @ts-ignore
target[targetProperty] = mock;

return () => {
target[targetProperty] = originalFn;
}
}
61 changes: 61 additions & 0 deletions tests/only.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import playwright, {expect} from "@playwright/test";
import {suite, test, only} from "../lib";
import {mockFn} from "./__mocks__/mockFn";

playwright.describe('@only decorator', () => {
playwright.describe('with @suite', () => {
const called: string[] = [];
const cleanup = mockFn(playwright.describe, 'only', () => called.push('playwright.describe.only()'));

playwright.afterAll(() => cleanup());

@only()
@suite()
class FocusedSuite {
}

// Unfortunately, we cannot call real `playwright.describe.only()` because other tests will not be run, so call needs to be mocked.
// As the result, we can only check if mocked `playwright.describe.only` was called.
playwright('@only decorator should run `playwright.describe.only()`', () => {
expect(called).toEqual(["playwright.describe.only()"]);
cleanup();
});
});

playwright.describe('with @test', () => {
const called: string[] = [];
const cleanup = mockFn(playwright, 'only', (fnName, test) => {
called.push('playwright.only()');
test();
});

playwright.afterAll(() => cleanup());

@suite()
class FocusedSuite {
@only()
@test()
async focusedTest() {
called.push('focusedTest');
}

@only()
@test()
async focusedTest2() {
called.push('focusedTest2');
}

@test()
async test() {
called.push('test');
}
}

// Unfortunately, we cannot call real `playwright.only()` because other tests will not be run, so call needs to be mocked.
// As the result, we can only check if mocked `playwright.only` was called.
playwright('@only decorator should run `playwright.only()` before each decorated test', () => {
expect(called).toEqual(['playwright.only()', 'focusedTest', 'playwright.only()', 'focusedTest2', 'test']); // playwright.only() is called before @only tests: focusedTest & focusedTest2, but not before test
cleanup();
});
});
});
22 changes: 5 additions & 17 deletions tests/slow.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
import playwright, {expect} from "@playwright/test";
import {suite, test, slow, beforeAll} from "../lib";

const mockSlow = (cb: (...args: any[]) => void) => {
const originalSlow = playwright.slow;

// @ts-ignore
playwright.slow = (...args) => {
cb(...args);
}

return () => {
playwright.slow = originalSlow;
}
}
import {suite, test, slow, beforeAll, only} from "../lib";
import { mockFn } from './__mocks__/mockFn';

playwright.describe('@slow decorator', () => {
playwright.describe('with @suite', () => {
const called: string[] = [];
const cleanup = mockSlow(() => called.push('playwright.slow()'));
const cleanup = mockFn(playwright, 'slow', () => called.push('playwright.slow()'));

playwright.afterAll(() => cleanup());

Expand Down Expand Up @@ -45,7 +33,7 @@ playwright.describe('@slow decorator', () => {
let cleanup: () => void;

playwright.beforeAll(() => {
cleanup = mockSlow(() => called.push('playwright.slow()'));
cleanup = mockFn(playwright, 'slow', () => called.push('playwright.slow()'));
});

playwright.afterAll(() => cleanup());
Expand All @@ -62,7 +50,7 @@ playwright.describe('@slow decorator', () => {
async slowTest() {
called.push('slowTest');
}

@test()
async test2() {
called.push('test2');
Expand Down

0 comments on commit 539ba8e

Please sign in to comment.