Skip to content

Commit

Permalink
Merge pull request #733 from qawolf/fix-crash
Browse files Browse the repository at this point in the history
Use qawolf.register to add qawolf script once before pages are loaded
  • Loading branch information
jperl committed Jul 13, 2020
2 parents 9255b74 + 31fb5a0 commit be40d70
Show file tree
Hide file tree
Showing 33 changed files with 265 additions and 451 deletions.
66 changes: 21 additions & 45 deletions src/create-code/ContextEventCollector.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,41 @@
import Debug from 'debug';
import { EventEmitter } from 'events';
import { BrowserContext } from 'playwright-core';
import { loadConfig } from '../config';
import { IndexedPage } from '../utils/context/indexPages';
import { isRegistered } from '../utils/context/register';
import { ElementEvent } from '../types';
import {
forEachPage,
IndexedPage,
indexPages,
initEvaluateScript,
} from '../utils';
import { QAWolfWeb } from '../web';
import { addScriptToContext } from '../web/addScript';

const debug = Debug('qawolf:ContextEventCollector');

export class ContextEventCollector extends EventEmitter {
readonly _attribute: string;
readonly _context: BrowserContext;

public static async create(
context: BrowserContext,
): Promise<ContextEventCollector> {
await addScriptToContext(context);
await indexPages(context);
return new ContextEventCollector(context);
const collector = new ContextEventCollector(context);
await collector._emitEvents();
return collector;
}

private _attribute: string;

protected constructor(context: BrowserContext) {
super();

this._attribute = loadConfig().attribute;

forEachPage(context, (page) =>
this._collectPageEvents(page as IndexedPage),
);
this._context = context;
}

private async _collectPageEvents(page: IndexedPage): Promise<void> {
if (page.isClosed()) return;

const index = page.createdIndex;
debug(`collect page events ${index} ${this._attribute}`);

await page.exposeFunction('cp_collectEvent', (event: ElementEvent) => {
debug(`emit %j`, event);
this.emit('elementevent', event);
});

await initEvaluateScript(
page,
({ attribute, pageIndex }) => {
const web: QAWolfWeb = (window as any).qawolf;

new web.PageEventCollector({
attribute,
pageIndex,
sendEvent: (window as any).cp_collectEvent,
});
},
{
attribute: this._attribute,
pageIndex: index,
async _emitEvents(): Promise<void> {
if (!isRegistered(this._context)) {
throw new Error('Use qawolf.register(context) first');
}

await this._context.exposeBinding(
'qawElementEvent',
({ page }, elementEvent: ElementEvent) => {
const pageIndex = (page as IndexedPage).createdIndex;
const event: ElementEvent = { ...elementEvent, page: pageIndex };
debug(`emit %j`, event);
this.emit('elementevent', event);
},
);
}
Expand Down
1 change: 0 additions & 1 deletion src/qawolf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export { Config } from './config';
export { create } from './create-code/create';

export {
interceptConsoleLogs,
launch,
register,
repl,
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export interface KeyEvent extends ElementEvent {
value: string;
}

export interface LogEvent {
level: string;
message: string;
}

export interface PasteEvent extends ElementEvent {
name: 'paste';
value: string;
Expand Down
14 changes: 7 additions & 7 deletions src/utils/context/indexPages.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import Debug from 'debug';
import { BrowserContext, Page } from 'playwright-core';

const debug = Debug('qawolf:indexPages');
export type IndexedPage = Page & {
createdIndex: number;
};

type IndexedBrowserContext = BrowserContext & {
_putilsIndexed: boolean;
_qawIndexed: boolean;
};

export type IndexedPage = Page & {
createdIndex: number;
};
const debug = Debug('qawolf:indexPages');

export const indexPages = async (context: BrowserContext): Promise<void> => {
/**
* Set page.createdIndex on pages.
*/
if ((context as IndexedBrowserContext)._putilsIndexed) return;
(context as IndexedBrowserContext)._putilsIndexed = true;
if ((context as IndexedBrowserContext)._qawIndexed) return;
(context as IndexedBrowserContext)._qawIndexed = true;

let index = 0;

Expand Down
70 changes: 70 additions & 0 deletions src/utils/context/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readFileSync } from 'fs';
import { BrowserContext } from 'playwright-core';
import { basename, join } from 'path';
import { loadConfig } from '../../config';
import { indexPages } from './indexPages';
import { Registry } from '../Registry';
import { saveArtifacts } from './saveArtifacts';

const scriptPath = require.resolve('../../../build/qawolf.web.js');
const webScript = readFileSync(scriptPath, 'utf8');

export type RegisteredBrowserContext = BrowserContext & {
_qawRegistered: boolean;
};

export const addInitScript = async (context: BrowserContext): Promise<void> => {
const attribute = JSON.stringify(loadConfig().attribute);

const script =
'(() => {\n' +
webScript +
`new qawolf.PageEventCollector({ attribute: ${attribute} });\n` +
'qawolf.interceptConsoleLogs();\n' +
'})();';

await context.addInitScript(script);
};

export const isRegistered = (context: BrowserContext): boolean => {
return !!(context as RegisteredBrowserContext)._qawRegistered;
};

export const getArtifactPath = (): string | null => {
let artifactPath = process.env.QAW_ARTIFACT_PATH;
if (!artifactPath) return null;

if (require.main) {
// store artifacts under the name of the main module, if there is one
// ex. /artifacts/search.test.js
artifactPath = join(artifactPath, basename(require.main.filename));
}

// store artifacts under the name of the browser being tested
const browserName = process.env.QAW_BROWSER;
if (browserName && artifactPath) {
artifactPath = join(artifactPath, browserName);
}

return artifactPath;
};

export const register = async (context: BrowserContext): Promise<void> => {
Registry.instance().setContext(context);

if (isRegistered(context)) return;
(context as RegisteredBrowserContext)._qawRegistered = true;

const promises: Promise<any>[] = [];

promises.push(addInitScript(context));

promises.push(indexPages(context));

const artifactPath = getArtifactPath();
if (artifactPath) {
promises.push(saveArtifacts(context, artifactPath));
}

await Promise.all(promises);
};
41 changes: 33 additions & 8 deletions src/utils/context/saveArtifacts.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,58 @@
import Debug from 'debug';
import { appendFileSync, ensureDir } from 'fs-extra';
import { join } from 'path';
import { BrowserContext } from 'playwright-core';
import { getFfmpegPath, saveVideo, PageVideoCapture } from 'playwright-video';
import { forEachPage } from './forEachPage';
import { saveConsoleLogs } from '../page/saveConsoleLogs';
import { IndexedPage } from './indexPages';
import { LogEvent } from '../../types';

const debug = Debug('qawolf:saveArtifacts');

const capturesToStop: PageVideoCapture[] = [];

export const saveArtifacts = (
export const saveConsoleLogs = async (
context: BrowserContext,
saveDir: string,
): Promise<void> => {
const logPath = new Map<number, string>();

await context.exposeBinding(
'qawLogEvent',
({ page }, { level, message }: LogEvent) => {
const pageIndex = (page as IndexedPage).createdIndex;
if (!logPath.has(pageIndex)) {
const timestamp = Date.now();
debug(`save logs for page ${pageIndex} at ${timestamp}`);
logPath.set(
pageIndex,
join(saveDir, `logs_${pageIndex}_${timestamp}.txt`),
);
}

appendFileSync(logPath.get(pageIndex), `${level}: ${message}\n`);
},
);
};

export const saveArtifacts = async (
context: BrowserContext,
saveDir: string,
): Promise<void> => {
// only record a video if ffmpeg installed
const includeVideo = !!getFfmpegPath();
let pageCount = 0;

return forEachPage(context, async (page) => {
await ensureDir(saveDir);

await saveConsoleLogs(context, saveDir);

await forEachPage(context, async (page) => {
const timestamp = Date.now();
const pageIndex = pageCount++;
debug(`save artifacts for page ${pageIndex} at ${timestamp}`);

try {
await saveConsoleLogs(
page,
join(saveDir, `logs_${pageIndex}_${timestamp}.txt`),
);

if (includeVideo) {
debug(`save video for page ${pageIndex}`);
const capture = await saveVideo(
Expand Down
2 changes: 1 addition & 1 deletion src/utils/context/waitForPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BrowserContext } from 'playwright-core';
import { isNull } from 'util';
import { indexPages, IndexedPage } from './indexPages';
import { IndexedPage, indexPages } from './indexPages';
import { waitFor } from '../waitFor';

export interface WaitForPageOptions {
Expand Down
12 changes: 6 additions & 6 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// context utils
export { forEachPage } from './context/forEachPage';
export { indexPages, IndexedPage } from './context/indexPages';
export { saveArtifacts, stopVideos } from './context/saveArtifacts';
export { register } from './context/register';
export {
saveArtifacts,
saveConsoleLogs,
stopVideos,
} from './context/saveArtifacts';
export { waitForPage, WaitForPageOptions } from './context/waitForPage';

// page utils
export { initEvaluateScript } from './page/initEvaluateScript';
export { interceptConsoleLogs } from './page/interceptConsoleLogs';
export { openScreenshot } from './page/openScreenshot';
export { saveConsoleLogs } from './page/saveConsoleLogs';
export { saveState } from './page/saveState';
export { scroll, ScrollOptions, ScrollValue } from './page/scroll';
export { setState } from './page/setState';
Expand All @@ -17,5 +18,4 @@ export { setState } from './page/setState';
export { getLaunchOptions, launch } from './launch';
export { repl } from './repl/repl';
export { Registry } from './Registry';
export { register } from './register';
export { waitFor } from './waitFor';
32 changes: 0 additions & 32 deletions src/utils/page/initEvaluateScript.ts

This file was deleted.

26 changes: 0 additions & 26 deletions src/utils/page/interceptConsoleLogs.ts

This file was deleted.

24 changes: 0 additions & 24 deletions src/utils/page/saveConsoleLogs.ts

This file was deleted.

Loading

0 comments on commit be40d70

Please sign in to comment.