Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add @only decorator #16

Merged
merged 3 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading