fix: do not pass unsafe `matcherResult` over IPC (#35565)

This commit is contained in:
Yury Semikhatsky 2025-04-10 14:39:33 -07:00 committed by GitHub
parent 24699d6bd0
commit adfad84bb8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 54 additions and 9 deletions

View File

@ -20,7 +20,6 @@ import { serializeCompilationCache } from '../transform/compilationCache';
import type { ConfigLocation, FullConfigInternal } from './config'; import type { ConfigLocation, FullConfigInternal } from './config';
import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test'; import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test';
import type { MatcherResultProperty } from '../matchers/matcherHint';
import type { SerializedCompilationCache } from '../transform/compilationCache'; import type { SerializedCompilationCache } from '../transform/compilationCache';
export type ConfigCLIOverrides = { export type ConfigCLIOverrides = {
@ -83,9 +82,7 @@ export type AttachmentPayload = {
stepId?: string; stepId?: string;
}; };
export type TestInfoErrorImpl = TestInfoError & { export type TestInfoErrorImpl = TestInfoError;
matcherResult?: MatcherResultProperty;
};
export type TestEndPayload = { export type TestEndPayload = {
testId: string; testId: string;

View File

@ -119,6 +119,13 @@ function sendMessageToParent(message: { method: string, params?: any }) {
try { try {
process.send!(message); process.send!(message);
} catch (e) { } catch (e) {
try {
// By default, the IPC messages are serialized as JSON.
JSON.stringify(message);
} catch {
// Always throw serialization errors.
throw e;
}
// Can throw when closing. // Can throw when closing.
} }
} }

View File

@ -14,14 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { ExpectError } from '../matchers/matcherHint';
import { serializeError } from '../util'; import { serializeError } from '../util';
import type { TestInfoErrorImpl } from '../common/ipc'; import type { TestInfoErrorImpl } from '../common/ipc';
export function testInfoError(error: Error | any): TestInfoErrorImpl { export function testInfoError(error: Error | any): TestInfoErrorImpl {
const result = serializeError(error); return serializeError(error);
if (error instanceof ExpectError)
result.matcherResult = error.matcherResult;
return result;
} }

View File

@ -617,3 +617,48 @@ test('should fail when test.fail.only passes unexpectedly', async ({ runInlineTe
expect(result.output).not.toContain('test1 should not run'); expect(result.output).not.toContain('test1 should not run');
expect(result.output).not.toContain('test3 should not run'); expect(result.output).not.toContain('test3 should not run');
}); });
test('should serialize circular objects', async ({ runInlineTest }) => {
const result = await runInlineTest({
'one-failure.spec.ts': `
import { test, expect } from '@playwright/test';
test('circular dependency', () => {
const a = {};
a.b = a;
expect(1).toEqual(a);
});
`
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.failed).toBe(1);
expect(result.output).toContain('Expected: {"b": [Circular]}');
});
test('should serialize BigInt', async ({ runInlineTest }) => {
const result = await runInlineTest({
'one-failure.spec.ts': `
import { test, expect } from '@playwright/test';
test('BigInt', () => {
expect(1).toEqual(1n);
});
`
});
expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1);
expect(result.output).toContain('Expected: 1n');
});
test('should report serialization error', async ({ runInlineTest }) => {
const result = await runInlineTest({
'one-failure.spec.ts': `
import { test, expect } from '@playwright/test';
test('function', () => {
expect(1).toEqual({ a: () => {} });
});
`
});
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.output).toContain('Expected: {\"a\": [Function a]}');
});