Skip to content

Commit

Permalink
feat(playwright-test): enable BrowseTheWebWithPlaywright to reuse an …
Browse files Browse the repository at this point in the history
…existing page instance

Reusing an existing Playwright page instance enables better integration with Playwright Test and
support for Playwright Component Testing

Related tickets: #1784
  • Loading branch information
jan-molak committed Jul 9, 2023
1 parent 0cef5a3 commit 5c2deb1
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 90 deletions.
@@ -1,19 +1,19 @@
import { Actor, Cast } from '@serenity-js/core';
import { ManageALocalServer } from '@serenity-js/local-server';
import { BrowseTheWebWithPlaywright, PlaywrightOptions } from '@serenity-js/playwright';
import { Browser } from 'playwright-core';
import * as playwright from 'playwright-core';

import { server } from './server';

export class ActorsWithLocalServer extends Cast {
constructor(private readonly browser: Browser, private readonly contextOptions: PlaywrightOptions) {
constructor(private readonly page: playwright.Page, private readonly contextOptions: PlaywrightOptions) {
super();
}

prepare(actor: Actor): Actor {
return actor.whoCan(
ManageALocalServer.runningAHttpListener(server),
BrowseTheWebWithPlaywright.using(this.browser, {
BrowseTheWebWithPlaywright.usingPage(this.page, {
userAgent: `${ actor.name }`,
...this.contextOptions,
}),
Expand Down
@@ -1,10 +1,18 @@
import { Ensure, equals } from '@serenity-js/assertions';
import { LocalServer, StartLocalServer, StopLocalServer } from '@serenity-js/local-server';
import { afterEach, describe, expect, it } from '@serenity-js/playwright-test';
import { afterEach, describe, expect, it, test } from '@serenity-js/playwright-test';
import { Navigate, Page } from '@serenity-js/web';

import { ActorsWithLocalServer } from './actors/ActorsWithLocalServer';

describe('Playwright Test integration', () => {

test.use({
actors: ({ page, contextOptions }, use) => {
use(new ActorsWithLocalServer(page, contextOptions));
},
});

describe('A screenplay scenario', () => {

it('receives a page object associated with the default actor', async ({ actor, page }) => {
Expand Down
@@ -1,10 +1,18 @@
import { Ensure, isTrue } from '@serenity-js/assertions';
import { LocalServer, StartLocalServer, StopLocalServer } from '@serenity-js/local-server';
import { afterEach, describe, it } from '@serenity-js/playwright-test';
import { afterEach, describe, it, test } from '@serenity-js/playwright-test';
import { Navigate } from '@serenity-js/web';

import { ActorsWithLocalServer } from './actors/ActorsWithLocalServer';

describe('Playwright Test reporting', () => {

test.use({
actors: ({ page, contextOptions }, use) => {
use(new ActorsWithLocalServer(page, contextOptions));
},
});

describe('A screenplay scenario', () => {

it('includes a screenshot when an interaction fails, by default', async ({ actor }) => {
Expand Down
@@ -1,9 +1,17 @@
import { LocalServer, StartLocalServer, StopLocalServer } from '@serenity-js/local-server';
import { afterEach, describe, it } from '@serenity-js/playwright-test';
import { afterEach, describe, it, test } from '@serenity-js/playwright-test';
import { Navigate } from '@serenity-js/web';

import { ActorsWithLocalServer } from './actors/ActorsWithLocalServer';

describe('Playwright Test reporting', () => {

test.use({
actors: ({ page, contextOptions }, use) => {
use(new ActorsWithLocalServer(page, contextOptions));
},
});

describe('A screenplay scenario', () => {

it('can include screenshots when the strategy is triggered', async ({ actor }) => {
Expand Down
10 changes: 0 additions & 10 deletions integration/playwright-test/playwright.config.ts
Expand Up @@ -2,7 +2,6 @@ import { devices, ReporterDescription } from '@playwright/test';
import { PlaywrightTestConfig } from '@serenity-js/playwright-test';
import * as path from 'path';

import { ActorsWithLocalServer } from './examples/screenplay/actors/ActorsWithLocalServer';
import { CustomCast } from './examples/screenplay/actors/CustomCast';

/**
Expand Down Expand Up @@ -69,9 +68,6 @@ const config: PlaywrightTestConfig = {
name: 'screenplay-local-server',
use: {
...devices['Desktop Chrome'],
actors: ({ browser, contextOptions }, use) => {
use(new ActorsWithLocalServer(browser, contextOptions));
},
defaultActorName: 'Phoebe',
},
},
Expand All @@ -96,9 +92,6 @@ const config: PlaywrightTestConfig = {
name: 'screenplay-photographer-default',
use: {
...devices['Desktop Chrome'],
actors: ({ browser, contextOptions }, use) => {
use(new ActorsWithLocalServer(browser, contextOptions));
},
defaultActorName: 'Phoebe',
},
},
Expand All @@ -109,9 +102,6 @@ const config: PlaywrightTestConfig = {
crew: [
[ '@serenity-js/web:Photographer', { strategy: 'TakePhotosOfInteractions' } ]
],
actors: ({ browser, contextOptions }, use) => {
use(new ActorsWithLocalServer(browser, contextOptions));
},
defaultActorName: 'Phoebe'
},
},
Expand Down
11 changes: 10 additions & 1 deletion integration/playwright-test/spec/native_page.spec.ts
@@ -1,6 +1,15 @@
import { expect, ifExitCodeIsOtherThan, logOutput, PickEvent } from '@integration/testing-tools';
import { Timestamp } from '@serenity-js/core';
import { SceneFinished, SceneFinishes, SceneStarts, SceneTagged, TestRunFinished, TestRunFinishes, TestRunnerDetected, TestRunStarts } from '@serenity-js/core/lib/events';
import {
SceneFinished,
SceneFinishes,
SceneStarts,
SceneTagged,
TestRunFinished,
TestRunFinishes,
TestRunnerDetected,
TestRunStarts
} from '@serenity-js/core/lib/events';
import { CorrelationId, ExecutionSuccessful, FeatureTag, Name } from '@serenity-js/core/lib/model';
import { describe, it } from 'mocha';

Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-test/src/api/SerenityOptions.ts
Expand Up @@ -44,7 +44,7 @@ import type { PlaywrightOptions } from '@serenity-js/playwright';
* ],
*
* // Register a custom cast of Serenity/JS actors
* // if you don't want to use the default one
* // if you don't want to use the default ones
* actors: ({ browser, contextOptions, apiUrl }, use) => {
* const cast = Cast.where(actor =>
* actor.whoCan(
Expand Down
136 changes: 65 additions & 71 deletions packages/playwright-test/src/api/test-api.ts
@@ -1,9 +1,8 @@
import type { TestInfo } from '@playwright/test';
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo, TestType } from '@playwright/test';
import { test as base } from '@playwright/test';
import { AnsiDiffFormatter, Cast, Duration, serenity as serenityInstance, TakeNotes } from '@serenity-js/core';
import { SceneFinishes, SceneTagged } from '@serenity-js/core/lib/events';
import { BrowserTag, PlatformTag } from '@serenity-js/core/lib/model';
import type { PlaywrightPage } from '@serenity-js/playwright';
import { BrowseTheWebWithPlaywright } from '@serenity-js/playwright';
import { Photographer, TakePhotosOfFailures } from '@serenity-js/web';
import * as os from 'os';
Expand All @@ -16,70 +15,12 @@ import type { SerenityFixtures } from './SerenityFixtures';
import type { SerenityOptions } from './SerenityOptions';
import type { SerenityTestType } from './SerenityTestType';

/**
* Declares a single test scenario.
*
* ## Example
*
* ```typescript
* import { Ensure, equals } from '@serenity-js/assertions'
* import { describe, it } from '@serenity-js/playwright-test'
*
* describe(`Todo List App`, () => {
*
* it(`should allow me to add a todo item`, async ({ actor }) => {
* await actor.attemptsTo(
* startWithAnEmptyList(),
*
* recordItem('Buy some milk'),
*
* Ensure.that(itemNames(), equals([
* 'Buy some milk',
* ])),
* )
* })
*
* it('supports multiple actors using separate browsers', async ({ actorCalled }) => {
* await actorCalled('Alice').attemptsTo(
* startWithAListContaining(
* 'Feed the cat'
* ),
* )
*
* await actorCalled('Bob').attemptsTo(
* startWithAListContaining(
* 'Walk the dog'
* ),
* )
*
* await actorCalled('Alice').attemptsTo(
* Ensure.that(itemNames(), equals([
* 'Feed the cat'
* ])),
* )
*
* await actorCalled('Bob').attemptsTo(
* Ensure.that(itemNames(), equals([
* 'Walk the dog'
* ])),
* )
* })
* })
* ```
*
* ## Learn more
* - {@apilink describe|Grouping test scenarios}
* - {@apilink SerenityFixtures}
* - [Playwright Test `test` function](https://playwright.dev/docs/api/class-test#test-call)
* - [Serenity/JS + Playwright Test project template](https://github.com/serenity-js/serenity-js-playwright-test-template/)
*/
export const it: SerenityTestType = base.extend<Omit<SerenityOptions, 'actors'> & SerenityFixtures>({

export const fixtures: Fixtures<Omit<SerenityOptions, 'actors'> & SerenityFixtures, object, PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions> = {
actors: [
async ({ browser, contextOptions, serenity }, use) => {

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async ({ contextOptions, page, serenity }, use): Promise<void> => {
await use(Cast.where(actor => actor.whoCan(
BrowseTheWebWithPlaywright.using(browser, contextOptions),
BrowseTheWebWithPlaywright.usingPage(page, contextOptions),
TakeNotes.usingAnEmptyNotepad(),
)))
},
Expand Down Expand Up @@ -191,14 +132,67 @@ export const it: SerenityTestType = base.extend<Omit<SerenityOptions, 'actors'>
actor: async ({ actorCalled, defaultActorName }, use) => {
await use(actorCalled(defaultActorName));
},
};

page: async ({ actor }, use) => {
const page = (await BrowseTheWebWithPlaywright.as(actor).currentPage()) as unknown as PlaywrightPage;
const nativePage = await page.nativePage();

await use(nativePage);
},
});
/**
* Declares a single test scenario.
*
* ## Example
*
* ```typescript
* import { Ensure, equals } from '@serenity-js/assertions'
* import { describe, it } from '@serenity-js/playwright-test'
*
* describe(`Todo List App`, () => {
*
* it(`should allow me to add a todo item`, async ({ actor }) => {
* await actor.attemptsTo(
* startWithAnEmptyList(),
*
* recordItem('Buy some milk'),
*
* Ensure.that(itemNames(), equals([
* 'Buy some milk',
* ])),
* )
* })
*
* it('supports multiple actors using separate browsers', async ({ actorCalled }) => {
* await actorCalled('Alice').attemptsTo(
* startWithAListContaining(
* 'Feed the cat'
* ),
* )
*
* await actorCalled('Bob').attemptsTo(
* startWithAListContaining(
* 'Walk the dog'
* ),
* )
*
* await actorCalled('Alice').attemptsTo(
* Ensure.that(itemNames(), equals([
* 'Feed the cat'
* ])),
* )
*
* await actorCalled('Bob').attemptsTo(
* Ensure.that(itemNames(), equals([
* 'Walk the dog'
* ])),
* )
* })
* })
* ```
*
* ## Learn more
* - {@apilink describe|Grouping test scenarios}
* - {@apilink SerenityFixtures}
* - [Playwright Test `test` function](https://playwright.dev/docs/api/class-test#test-call)
* - [Serenity/JS + Playwright Test project template](https://github.com/serenity-js/serenity-js-playwright-test-template/)
*/
export const it: SerenityTestType = (base as TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>)
.extend<Omit<SerenityOptions, 'actors'> & SerenityFixtures>(fixtures);

function asDuration(maybeDuration: number | Duration): Duration {
return maybeDuration instanceof Duration
Expand Down

0 comments on commit 5c2deb1

Please sign in to comment.