Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions __tests__/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('options', () => {
params: {},
screenshots: 'on',
});

expect(await normalizeOptions(cliArgs)).toMatchObject({
dryRun: true,
environment: 'test',
Expand Down Expand Up @@ -187,4 +188,14 @@ describe('options', () => {
expect(Buffer.isBuffer(t.passphrase)).toBeFalsy();
});
});

it('normalizes outputDom option', async () => {
// Test that outputDom is false by default
let options = await normalizeOptions({});
expect(options.outputDom).toBe(false);

// Test that outputDom is set when true in CLI args
options = await normalizeOptions({ outputDom: true });
expect(options.outputDom).toBe(true);
});
});
36 changes: 36 additions & 0 deletions __tests__/reporters/base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,40 @@ describe('base reporter', () => {
reporter.onEnd();
expect((await readAndCloseStream()).toString()).toMatchSnapshot();
});

it('should not print DOM when outputDom is disabled', async () => {
const j1 = tJourney();
reporter.onJourneyStart(j1, { timestamp });
reporter.onJourneyEnd(j1, {
timestamp,
browserDelay: 0,
options: {},
pageDom: '<html><body>Test DOM</body></html>',
});
reporter.onEnd();
const output = await readAndCloseStream();
expect(output).not.toContain('PAGE DOM START');
expect(output).not.toContain('<html><body>Test DOM</body></html>');
});

it('should print DOM when outputDom is enabled', async () => {
const reporter = new BaseReporter({
fd: fs.openSync(dest, 'w'),
outputDom: true,
});
stream = reporter.stream;

const j1 = tJourney();
reporter.onJourneyStart(j1, { timestamp });
reporter.onJourneyEnd(j1, {
timestamp,
browserDelay: 0,
options: {},
pageDom: '<html><body>Test DOM</body></html>',
});
reporter.onEnd();
const output = await readAndCloseStream();
expect(output).toContain('PAGE DOM START');
expect(output).toContain('<html><body>Test DOM</body></html>');
});
});
6 changes: 6 additions & 0 deletions __tests__/utils/jest-global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ import { spawn } from 'child_process';
import { wsEndpoint } from './test-config';

module.exports = async () => {
// Unset the SYNTHETICS_API_KEY to ensure it doesn't affect tests
if (process.env.SYNTHETICS_API_KEY) {
console.log('Unsetting SYNTHETICS_API_KEY environment variable for tests');
delete process.env.SYNTHETICS_API_KEY;
}

if (wsEndpoint) {
return new Promise((resolve, reject) => {
console.log(`\nRunning BrowserService ${wsEndpoint}`);
Expand Down
1 change: 1 addition & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ program
'--ignore-https-errors',
'ignores any HTTPS errors in sites being tested, including ones related to unrecognized certs or signatures. This can be insecure!'
)
.option('--output-dom', 'output the full page DOM when a journey ends')
.option(
'--quiet-exit-code',
'always return 0 as an exit code status, regardless of test pass / fail. Only return > 0 exit codes on internal errors where the suite could not be run'
Expand Down
3 changes: 3 additions & 0 deletions src/common_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export type CliArgs = BaseArgs & {
headless?: boolean;
capability?: Array<string>;
ignoreHttpsErrors?: boolean;
outputDom?: boolean;
};

export type RunOptions = BaseArgs & {
Expand All @@ -248,6 +249,7 @@ export type RunOptions = BaseArgs & {
filmstrips?: boolean;
environment?: string;
networkConditions?: NetworkConditions;
outputDom?: boolean;
reporter?: BuiltInReporterName | ReporterInstance;
grepOpts?: GrepOptions;
};
Expand Down Expand Up @@ -322,6 +324,7 @@ export type JourneyEndResult = JourneyStartResult &
JourneyResult & {
browserDelay: number;
options: RunOptions;
pageDom?: string;
};

export type StepEndResult = StepResult;
21 changes: 19 additions & 2 deletions src/core/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,12 @@ export default class Runner implements RunnerInfo {
* Set up the corresponding reporter and fallback
* to default reporter if not provided
*/
const { reporter, outfd, dryRun } = options;
const { reporter, outfd, dryRun, outputDom } = options;
const Reporter =
typeof reporter === 'function'
? reporter
: reporters[reporter] || reporters['default'];
this.#reporter = new Reporter({ fd: outfd, dryRun });
this.#reporter = new Reporter({ fd: outfd, dryRun, outputDom });
}

async #runBeforeAllHook(args: HooksArgs) {
Expand Down Expand Up @@ -338,12 +338,29 @@ export default class Runner implements RunnerInfo {
pOutput.browserconsole,
journey.status
);

// Get the current page DOM if available
let pageDom: string | undefined;
if (this.#driver && this.#driver.page) {
try {
// Get the last active page
const pages = this.#driver.context.pages();
const page = pages[pages.length - 1];
if (page) {
pageDom = await page.content();
}
} catch (error) {
log('Error capturing page DOM: ' + error);
}
}

await this.#reporter?.onJourneyEnd?.(journey, {
browserDelay: this.#browserDelay,
timestamp: getTimestamp(),
options,
networkinfo: pOutput.networkinfo,
browserconsole: bConsole,
pageDom,
});
await Gatherer.endRecording();
await Gatherer.dispose(this.#driver);
Expand Down
1 change: 1 addition & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export async function normalizeOptions(
}

options.screenshots = cliArgs.screenshots ?? 'on';
options.outputDom = cliArgs.outputDom ?? false;
break;
case 'push':
/**
Expand Down
22 changes: 21 additions & 1 deletion src/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default class BaseReporter implements Reporter {
stream: SonicBoom;
fd: number;
dryRun: boolean;
outputDom: boolean;
metrics = {
succeeded: 0,
failed: 0,
Expand All @@ -53,6 +54,7 @@ export default class BaseReporter implements Reporter {
constructor(options: ReporterOptions = {}) {
this.fd = options.fd || process.stdout.fd;
this.dryRun = options.dryRun ?? false;
this.outputDom = options.outputDom ?? false;
/**
* minLength is set to 1 byte to make sure we flush the
* content even if its the last byte on the stream buffer
Expand Down Expand Up @@ -84,7 +86,7 @@ export default class BaseReporter implements Reporter {
}

/* eslint-disable @typescript-eslint/no-unused-vars */
onJourneyEnd(journey: Journey, {}: JourneyEndResult) {
onJourneyEnd(journey: Journey, { pageDom }: JourneyEndResult) {
const { failed, succeeded, skipped } = this.metrics;
const total = failed + succeeded + skipped;
/**
Expand All @@ -95,6 +97,24 @@ export default class BaseReporter implements Reporter {
const message = renderError(serializeError(journey.error));
this.write(indent(message) + '\n');
}

// Log the full page DOM when available and outputDom option is enabled
if (this.outputDom && pageDom) {
this.write(indent('\n--- PAGE DOM START ---'));
// If DOM is too large, truncate it to a reasonable size
const maxDomLength = 100000; // Limit to ~100KB
if (pageDom.length > maxDomLength) {
this.write(
indent(
pageDom.substring(0, maxDomLength) +
'\n... (DOM truncated due to large size)'
)
);
} else {
this.write(indent(pageDom));
}
this.write(indent('--- PAGE DOM END ---\n'));
}
}

onEnd() {
Expand Down
1 change: 1 addition & 0 deletions src/reporters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type ReporterOptions = {
fd?: number;
colors?: boolean;
dryRun?: boolean;
outputDom?: boolean;
};
export type BuiltInReporterName =
| 'default'
Expand Down