Skip to content

Commit

Permalink
feat(playwright-test): introducing Component Testing with Serenity/JS…
Browse files Browse the repository at this point in the history
… and Playwright Test

It's now possible to use Serenity/JS reporting capabilities and Serenity/JS Screenplay Pattern APIs
to write UI Component Tests

Related tickets: #1784
  • Loading branch information
jan-molak committed Jul 11, 2023
1 parent 3c9aa4b commit 7b3c6c8
Show file tree
Hide file tree
Showing 18 changed files with 1,651 additions and 127 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yaml
Expand Up @@ -139,6 +139,7 @@ jobs:
modules: |
[
"playwright-test",
"playwright-test-ct",
"playwright-web"
]
runs-on: 'ubuntu-latest'
Expand Down
9 changes: 9 additions & 0 deletions integration/playwright-test-ct/.gitignore
@@ -0,0 +1,9 @@
# Node
node_modules
*.log

# Build artifacts
/target
/test-results/
/playwright-report/
/playwright/.cache/
42 changes: 42 additions & 0 deletions integration/playwright-test-ct/package.json
@@ -0,0 +1,42 @@
{
"name": "@integration/playwright-test-ct",
"version": "3.0.0",
"description": "Playwright Test CT integration tests",
"author": {
"name": "Jan Molak",
"email": "jan.molak@smartcodeltd.co.uk",
"url": "https://janmolak.com"
},
"homepage": "https://serenity-js.org",
"license": "Apache-2.0",
"private": true,
"config": {
"access": "private"
},
"scripts": {
"clean": "rimraf playwright-report playwright/.cache",
"test": "playwright test --config playwright-ct.config.ts"
},
"repository": {
"type": "git",
"url": "https://github.com/serenity-js/serenity-js.git"
},
"bugs": {
"url": "https://github.com/serenity-js/serenity-js/issues"
},
"engines": {
"node": "^16.13 || ^18.12 || ^20"
},
"dependencies": {
"@integration/testing-tools": "3.0.0",
"@playwright/experimental-ct-react17": "^1.35.1",
"@serenity-js/assertions": "^3.0.0",
"@serenity-js/core": "^3.0.0",
"@serenity-js/playwright": "^3.0.0",
"@serenity-js/playwright-test": "^3.0.0",
"@serenity-js/web": "^3.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"typescript": "^5.1.6"
}
}
48 changes: 48 additions & 0 deletions integration/playwright-test-ct/playwright-ct.config.ts
@@ -0,0 +1,48 @@
import { defineConfig, devices } from '@playwright/experimental-ct-react17';

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './',
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: './__snapshots__',
/* Maximum time one test can run for. */
timeout: 10 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',

/* Port to use for Playwright component endpoint. */
ctPort: 3100,

headless: true
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
],
});
12 changes: 12 additions & 0 deletions integration/playwright-test-ct/playwright/index.html
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions integration/playwright-test-ct/playwright/index.tsx
@@ -0,0 +1,2 @@
// Import styles, initialize component theme here.
// import '../src/common.css';
18 changes: 18 additions & 0 deletions integration/playwright-test-ct/src/UppercaseInput.tsx
@@ -0,0 +1,18 @@
import React, { useState } from 'react';

const UppercaseInput = () => {
const [inputText, setInputText] = useState('');

const handleChange = (e) => {
setInputText(e.target.value);
};

return (
<div className={'example-input'}>
<input type="text" value={inputText} onChange={handleChange} />
<p className={'output'}>{inputText.toUpperCase()}</p>
</div>
);
};

export default UppercaseInput;
69 changes: 69 additions & 0 deletions integration/playwright-test-ct/src/component-testing.spec.tsx
@@ -0,0 +1,69 @@
import { test as componentTest } from '@playwright/experimental-ct-react17';
import { Ensure, equals } from '@serenity-js/assertions';
import { expect, useBase } from '@serenity-js/playwright-test';
import { By, ByDeepCss, Enter, PageElement, Text, Value } from '@serenity-js/web';

import UppercaseInput from './UppercaseInput';

const { it, describe } = useBase(componentTest).useFixtures<{ emailAddress: string }>({
emailAddress: ({ actor }, use) => {
use(`${ actor.name.toLowerCase() }@example.org`)
}
})

describe('Serenity/JS', () => {

it('works with native Playwright component tests', async ({ mount }) => {
const nativeComponent = await mount(<UppercaseInput/>);

const input = nativeComponent.locator('input');
const output = nativeComponent.locator('.output');

await expect(input).toHaveValue('');
await input.fill('Hello');
await expect(input).toHaveValue('Hello');
await expect(output).toHaveText('HELLO');
});

it('works with custom fixtures', ({ emailAddress }) => {
expect(emailAddress).toEqual('serena@example.org');
});

describe('PageElement', () => {

it('can wrap a native component', async ({ actor, mount }) => {
const nativeComponent = await mount(<UppercaseInput/>);

const component = PageElement.from(nativeComponent);

const selector = await actor.answer(component.locator.selector)
expect(selector).toBeInstanceOf(ByDeepCss);
expect((selector as ByDeepCss).value).toEqual('#root >> internal:control=component');
});

it('allows for chaining PageElements with wrapped native elements', async ({ actor, mount }) => {
const nativeComponent = await mount(<UppercaseInput/>);

const component = PageElement.from(nativeComponent);
const input = PageElement.located(By.css('input')).of(component);

const nativeInput = await actor.answer(input.nativeElement())
expect((nativeInput as any)._selector).toEqual('#root >> internal:control=component >> :light(input)');
});

it('enables interactions with native components', async ({ actor, mount }) => {
const nativeComponent = await mount(<UppercaseInput/>);

const component = PageElement.from(nativeComponent);
const input = PageElement.located(By.css('input')).of(component);
const output = PageElement.located(By.css('.output')).of(component);

await actor.attemptsTo(
Ensure.that(Value.of(input), equals('')),
Enter.theValue('Hello').into(input),
Ensure.that(Value.of(input), equals('Hello')),
Ensure.that(Text.of(output), equals('HELLO')),
);
});
});
});
13 changes: 13 additions & 0 deletions integration/playwright-test-ct/tsconfig.json
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.options.json",

"compilerOptions": {
"jsx": "react-jsx",
"esModuleInterop": true
},

"include": [
"src/**/*.ts",
"src/**/*.tsx",
]
}

0 comments on commit 7b3c6c8

Please sign in to comment.