Skip to content

Commit

Permalink
Merge pull request #372 from cipivanov/master
Browse files Browse the repository at this point in the history
serenity-js/serenity-js#493: Alternatives to Protractor: Playwright Prototype
  • Loading branch information
jan-molak authored Feb 5, 2021
2 parents a748db5 + c894832 commit 4d5dc1b
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 4 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ exports.config = {

Import the `authenticator-browser-extension` and generate an expanded `Authenticator` web extension directory before launching a Puppeteer browser:

```javascript
```typescript
// puppeteer/chrome-authenticator-extension.spec.ts
const { Authenticator } = require('authenticator-browser-extension');

const authenticator = Authenticator.for('admin', 'Password123')
Expand All @@ -114,6 +115,32 @@ browser = await puppeteer.launch({
});
```

### Playwright

Requires launching a [persistent browser context instance](https://playwright.dev/docs/api/class-browsertype?_highlight=persistent#browsertypelaunchpersistentcontextuserdatadir-options) containing the `Authenticator` extension. In every other way a carbon copy of the Puppeteer prototype.

```typescript
// playwright/chrome-authenticator-extension.spec.ts
const extensionDirectory = `${process.cwd()}/build/playwright/authenticator`;

const authenticator = Authenticator.for(
'admin',
'Password123'
).asDirectoryAt(extensionDirectory);

browser = await playwright['chromium'].launchPersistentContext(
extensionDirectory,
{
args: [
`--disable-extensions-except=${authenticator}`,
`--load-extension=${authenticator}`,
`--no-sandbox`,
],
headless: false,
}
);
```

## Known limitations

### Chrome headless
Expand Down
135 changes: 135 additions & 0 deletions e2e/playwright/chrome-authenticator-extension.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-use-before-define,@typescript-eslint/no-explicit-any */

import { Ensure, equals } from '@serenity-js/assertions';
import { Ability, Actor, actorCalled, Cast, engage, Interaction, Question, UsesAbilities } from '@serenity-js/core';
import { LocalServer, ManageALocalServer, StartLocalServer, StopLocalServer } from '@serenity-js/local-server';
import { Browser, ElementHandle, Page, Response } from 'playwright';

import { Authenticator } from '../../src';
import { TestApp } from '../TestApp';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const playwright = require('playwright');

let page: Page;
let browser: Browser;

describe('Chrome Authenticator Extension, when used with Playwright', function () {
this.timeout(15000);

before(async () => {
const extensionDirectory = `${process.cwd()}/build/playwright/authenticator`;

const authenticator = Authenticator.for(
'admin',
'Password123'
).asDirectoryAt(extensionDirectory);

browser = await playwright['chromium'].launchPersistentContext(
extensionDirectory,
{
args: [
`--disable-extensions-except=${authenticator}`,
`--load-extension=${authenticator}`,
`--no-sandbox`,
],
headless: false,
}
);
page = await browser.newPage();
});

class Actors implements Cast {
prepare(actor: Actor): Actor {
return actor.whoCan(
BrowseTheWeb.using(page),
ManageALocalServer.runningAHttpListener(
TestApp.allowingUsersAuthenticatedWith({
username: 'admin',
password: 'Password123',
})
)
);
}
}

beforeEach(() => engage(new Actors()));
beforeEach(() =>
actorCalled('Dave').attemptsTo(StartLocalServer.onRandomPort())
);

it(`enables a Chrome web browser-based test to authenticate with a web app`, () =>
actorCalled('Dave').attemptsTo(
Navigate.to(LocalServer.url()),
Ensure.that(Text.of(TestPage.Title), equals('Authenticated!'))
));

after(async () => await browser.close());
after(() => actorCalled('Dave').attemptsTo(StopLocalServer.ifRunning()));
});

// Serenity/JS doesn't support Playwright natively yet.
// However, below is a minimalists proof-of-concept Screenplay Pattern-style integration code
// that brings the two frameworks together.
//
// If you'd like Serenity/JS to support Playwright out of the box, please:
// - vote on https://github.com/serenity-js/serenity-js/issues/493
// - ask your boss to sponsor this feature - https://github.com/sponsors/serenity-js

const Navigate = {
to: (url: Question<string>) =>
Interaction.where(
`#actor navigates to ${url}`,
(actor) =>
actor
.answer(url)
.then((actualUrl) => BrowseTheWeb.as(actor).get(actualUrl))
.then((_: Response | null) => void 0) // eslint-disable-line @typescript-eslint/no-unused-vars
),
};

const Target = {
the: (name: string) => ({
locatedBy: (selector: string) =>
Question.about<Promise<ElementHandle | null>>(`the ${name}`, (actor) =>
BrowseTheWeb.as(actor).locate(selector)
),
}),
};

const Text = {
of: (target: Question<Promise<ElementHandle | null>>) =>
Question.about<Promise<string>>(`text of ${target}`, (actor) =>
actor.answer(target).then((element) => {
return page.evaluate(
(actualElement) => actualElement.textContent,
element
);
})
),
};

const TestPage = {
Title: Target.the('header').locatedBy('h1'),
};

class BrowseTheWeb implements Ability {
static using(browserInstance: Page) {
return new BrowseTheWeb(browserInstance);
}

static as(actor: UsesAbilities): BrowseTheWeb {
return actor.abilityTo(BrowseTheWeb);
}

constructor(private readonly page: Page) {
}

get(destination: string): Promise<Response | null> {
return this.page.goto(destination);
}

locate(selector: string): Promise<ElementHandle | null> {
return this.page.$(selector);
}
}
88 changes: 86 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
"lint:fix": "eslint . --ext ts --fix",
"compile": "tsc --project tsconfig.json",
"test:spec": "nyc --report-dir ./reports/coverage mocha --require ts-node/register --reporter spec 'spec/**/*.spec.ts'",
"test:e2e": "npm run test:e2e:protractor && npm run test:e2e:wdio && npm run test:e2e:puppeteer",
"test:e2e": "npm run test:e2e:protractor && npm run test:e2e:wdio && npm run test:e2e:puppeteer && npm run test:e2e:playwright",
"test:e2e:protractor": "protractor e2e/protractor/protractor.conf.js",
"test:e2e:wdio": "wdio e2e/webdriverio/wdio.conf.js",
"test:e2e:playwright": "mocha --require ts-node/register --reporter spec e2e/playwright/chrome-authenticator-extension.spec.ts",
"test:e2e:puppeteer": "mocha --require ts-node/register --reporter spec e2e/puppeteer/chrome-authenticator-extension.spec.ts",
"test": "npm run test:spec",
"verify": "npm run clean && npm run lint && npm run test:spec && npm run compile && npm run test:e2e",
Expand Down Expand Up @@ -75,6 +76,7 @@
"mocha": "^8.2.1",
"mocha-testdata": "^1.2.0",
"nyc": "^15.1.0",
"playwright": "^1.8.0",
"protractor": "^7.0.0",
"puppeteer": "^7.0.1",
"rimraf": "^3.0.2",
Expand Down

0 comments on commit 4d5dc1b

Please sign in to comment.