impl pageActions
This commit is contained in:
Родитель
a94bb7d517
Коммит
ac5bae4877
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible Node.js debug attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Unit Jest Tests",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"--inspect-brk",
|
||||
"${workspaceRoot}/node_modules/.bin/jest",
|
||||
"--runInBand",
|
||||
"--watch",
|
||||
"--config",
|
||||
"${workspaceRoot}/packages/flamegrill/jest.config.js",
|
||||
"${fileBasenameNoExtension}"
|
||||
],
|
||||
"windows": {
|
||||
"args": [
|
||||
"--inspect-brk",
|
||||
"${workspaceRoot}/node_modules/jest/bin/jest.js",
|
||||
"--runInBand",
|
||||
"--watch",
|
||||
"--config",
|
||||
"${workspaceRoot}/packages/flamegrill/jest.config.js",
|
||||
"${fileBasenameNoExtension}"
|
||||
]
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"type": "minor",
|
||||
"comment": "support executing page operations before taking measures/profiling",
|
||||
"comment": "Support customized page actions and reset default timeout to 30s instead of no timeout",
|
||||
"packageName": "flamegrill",
|
||||
"email": "xgao@microsoft.com",
|
||||
"commit": "355b574566a201ce7c8e31be9c55472651c85310",
|
||||
|
|
|
@ -16,13 +16,22 @@ export interface Scenarios {
|
|||
[scenarioName: string]: Scenario;
|
||||
};
|
||||
|
||||
export type ScenarioConfig = {
|
||||
export interface PageActionOptions {
|
||||
/** URL the page will navigate to. */
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async page operations which will be execute before taking metrics.
|
||||
* This will override default page operations done by flamegrill before page.metrics().
|
||||
*/
|
||||
export type PageActions = (page: ProfilePage, options: PageActionOptions) => Promise<void>;
|
||||
|
||||
export interface ScenarioConfig {
|
||||
outDir?: string;
|
||||
tempDir?: string;
|
||||
|
||||
/** Any async operation which will be execute before taking metrics for the profiling page. */
|
||||
executeBeforeMeasurement?(page: ProfilePage): Promise<void>;
|
||||
};
|
||||
pageActions?: PageActions;
|
||||
}
|
||||
|
||||
export interface CookResult {
|
||||
profile: ScenarioProfile;
|
||||
|
@ -58,7 +67,7 @@ export async function cook(scenarios: Scenarios, userConfig?: ScenarioConfig): P
|
|||
const config = {
|
||||
outDir: userConfig && userConfig.outDir ? resolveDir(userConfig.outDir) : process.cwd(),
|
||||
tempDir: userConfig && userConfig.tempDir ? resolveDir(userConfig.tempDir) : process.cwd(),
|
||||
executeBeforeMeasurement: userConfig && userConfig.executeBeforeMeasurement,
|
||||
pageActions: userConfig && userConfig.pageActions,
|
||||
};
|
||||
|
||||
const profiles = await profile(scenarios, config);
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
import * as tmp from 'tmp';
|
||||
import { Browser, Page } from 'puppeteer';
|
||||
|
||||
import { __unitTestHooks, ProfilePage } from '../profile';
|
||||
import { __unitTestHooks, ProfilePage, Profile } from '../profile';
|
||||
import { PageActions, PageActionOptions } from '../../flamegrill';
|
||||
|
||||
describe('profileUrl', () => {
|
||||
const { profileUrl } = __unitTestHooks;
|
||||
const testUrl = 'testUrl';
|
||||
const testMetrics = { metric1: 1, metric2: 2 };
|
||||
const testPage: Page = {
|
||||
close: jest.fn(() => Promise.resolve()),
|
||||
goto: jest.fn(() => {
|
||||
return Promise.resolve(null);
|
||||
}),
|
||||
metrics: jest.fn(() => Promise.resolve(testMetrics)),
|
||||
setDefaultTimeout: jest.fn(() => {}),
|
||||
waitForSelector: jest.fn(() => Promise.resolve()),
|
||||
} as unknown as Page;
|
||||
let testPage: Page;
|
||||
|
||||
const testBrowser: Browser = {
|
||||
newPage: jest.fn(() => {
|
||||
|
@ -35,12 +28,44 @@ describe('profileUrl', () => {
|
|||
|
||||
afterAll(() => {
|
||||
outdir.removeCallback();
|
||||
})
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
testPage = {
|
||||
close: jest.fn(() => Promise.resolve()),
|
||||
goto: jest.fn(() => {
|
||||
return Promise.resolve(null);
|
||||
}),
|
||||
metrics: jest.fn(() => Promise.resolve(testMetrics)),
|
||||
setDefaultTimeout: jest.fn(() => {}),
|
||||
waitForSelector: jest.fn(() => Promise.resolve()),
|
||||
} as unknown as Page;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('performs expected operations', async () => {
|
||||
const executeBeforeMeasurement = async (page: ProfilePage) => { await page.waitForSelector(testSelector); }
|
||||
const result = await profileUrl(testBrowser, testUrl, 'testScenario', outdir.name);
|
||||
|
||||
const result = await profileUrl(testBrowser, testUrl, 'testScenario', outdir.name, executeBeforeMeasurement);
|
||||
expect((testPage.goto as jest.Mock).mock.calls.length).toEqual(1);
|
||||
expect((testPage.goto as jest.Mock).mock.calls[0][0]).toEqual(testUrl);
|
||||
expect((testPage.close as jest.Mock).mock.calls.length).toEqual(1);
|
||||
|
||||
expect(logfile).toBeDefined();
|
||||
expect(result.logFile).toEqual(logfile.name);
|
||||
expect(result.metrics).toEqual(testMetrics);
|
||||
});
|
||||
|
||||
it('performs expected operations when user defined page actions', async () => {
|
||||
const pageActions: PageActions = async (page: ProfilePage, options: PageActionOptions) => {
|
||||
await page.setDefaultTimeout(0);
|
||||
await page.goto(options.url);
|
||||
await page.waitForSelector(testSelector);
|
||||
};
|
||||
|
||||
const result = await profileUrl(testBrowser, testUrl, 'testScenario', outdir.name, pageActions);
|
||||
|
||||
expect((testPage.setDefaultTimeout as jest.Mock).mock.calls.length).toEqual(1);
|
||||
expect((testPage.setDefaultTimeout as jest.Mock).mock.calls[0][0]).toEqual(0);
|
||||
|
|
|
@ -3,7 +3,7 @@ import path from 'path';
|
|||
import puppeteer from 'puppeteer';
|
||||
import { Browser, Metrics } from 'puppeteer';
|
||||
|
||||
import { Scenarios, ScenarioConfig } from '../flamegrill';
|
||||
import { Scenarios, ScenarioConfig, PageActions } from '../flamegrill';
|
||||
|
||||
import { arr_diff } from '../util';
|
||||
|
||||
|
@ -24,7 +24,7 @@ export type ProfilePage = puppeteer.Page;
|
|||
|
||||
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
|
||||
|
||||
export type ScenarioProfileConfig = Optional<Required<ScenarioConfig>, 'executeBeforeMeasurement'>;
|
||||
export type ScenarioProfileConfig = Optional<Required<ScenarioConfig>, 'pageActions'>;
|
||||
|
||||
// const extraV8Flags = '--log-source-code --log-timer-events';
|
||||
// const extraV8Flags = '--log-source-code';
|
||||
|
@ -37,7 +37,7 @@ const extraV8Flags = '';
|
|||
* @param scenarios
|
||||
*/
|
||||
export async function profile(scenarios: Scenarios, config: ScenarioProfileConfig): Promise<ScenarioProfiles> {
|
||||
const { tempDir, executeBeforeMeasurement } = config;
|
||||
const { tempDir, pageActions } = config;
|
||||
|
||||
const logFile = path.join(tempDir, '/puppeteer.log');
|
||||
console.log(`profile logFile: ${logFile}`);
|
||||
|
@ -65,10 +65,10 @@ export async function profile(scenarios: Scenarios, config: ScenarioProfileConfi
|
|||
for (const scenarioName of Object.keys(scenarios)) {
|
||||
const scenario = scenarios[scenarioName];
|
||||
|
||||
let profileResults: ScenarioProfile = await profileUrl(browser, scenario.scenario, scenarioName, tempDir, executeBeforeMeasurement);
|
||||
let profileResults: ScenarioProfile = await profileUrl(browser, scenario.scenario, scenarioName, tempDir, pageActions);
|
||||
|
||||
if (scenario.baseline) {
|
||||
profileResults.baseline = await profileUrl(browser, scenario.baseline, scenarioName, tempDir, executeBeforeMeasurement);
|
||||
profileResults.baseline = await profileUrl(browser, scenario.baseline, scenarioName, tempDir, pageActions);
|
||||
}
|
||||
|
||||
profiles[scenarioName] = profileResults;
|
||||
|
@ -87,7 +87,7 @@ export async function profile(scenarios: Scenarios, config: ScenarioProfileConfi
|
|||
* @param {string} testUrl Base URL supporting 'scenario' and 'iterations' query parameters.
|
||||
* @param {string} profileName Name of scenario that will be used with baseUrl.
|
||||
* @param {string} logDir Absolute path to output log profiles.
|
||||
* @param onPageLoad Async opertaion that is executed after page is loaded.
|
||||
* @param {PageActions} pageActions Async opertaion that is executed before taking metrics.
|
||||
* @returns {string} Log file path associated with test.
|
||||
*/
|
||||
async function profileUrl(
|
||||
|
@ -95,17 +95,12 @@ async function profileUrl(
|
|||
testUrl: string,
|
||||
profileName: string,
|
||||
logDir: string,
|
||||
onPageLoad?: (page: ProfilePage) => Promise<void>
|
||||
pageActions?: PageActions
|
||||
): Promise<Profile> {
|
||||
const logFilesBefore = fs.readdirSync(logDir);
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Default timeout is 30 seconds. This is good for most tests except for problematic components like DocumentCardTitle.
|
||||
// Disable timeout for now and tweak to a maximum setting once server conditions are better known.
|
||||
// TODO: argument? should probably default to 30 seconds
|
||||
page.setDefaultTimeout(0);
|
||||
|
||||
const logFilesAfter = fs.readdirSync(logDir);
|
||||
|
||||
const testLogFile = arr_diff(logFilesBefore, logFilesAfter);
|
||||
|
@ -121,14 +116,13 @@ async function profileUrl(
|
|||
console.log(`Starting test for ${profileName} at ${testUrl}`);
|
||||
|
||||
console.time('Ran profile in');
|
||||
// TODO: consider using or exposing other load finished options:
|
||||
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagegotourl-options
|
||||
await page.goto(testUrl);
|
||||
|
||||
if (onPageLoad) {
|
||||
if (pageActions) {
|
||||
console.log("Started executing user-defined page operations.");
|
||||
await onPageLoad(page);
|
||||
await pageActions(page, { url: testUrl });
|
||||
console.log("Finished executing user-defined page operations.");
|
||||
} else {
|
||||
await page.goto(testUrl);
|
||||
}
|
||||
|
||||
console.timeEnd('Ran profile in');
|
||||
|
|
Загрузка…
Ссылка в новой задаче