diff --git a/packages/playwright-core/src/tools/backend/context.ts b/packages/playwright-core/src/tools/backend/context.ts
index d95462452d37f..8d0c7399dcc9b 100644
--- a/packages/playwright-core/src/tools/backend/context.ts
+++ b/packages/playwright-core/src/tools/backend/context.ts
@@ -353,6 +353,15 @@ export class Context {
code: `process.env['${secretName}']`,
};
}
+
+ redactSecrets(text: string): string {
+ for (const [secretName, secretValue] of Object.entries(this.config.secrets ?? {})) {
+ if (!secretValue)
+ continue;
+ text = text.replaceAll(secretValue, `${secretName}`);
+ }
+ return text;
+ }
}
function originOrHostGlob(originOrHost: string) {
diff --git a/packages/playwright-core/src/tools/backend/logFile.ts b/packages/playwright-core/src/tools/backend/logFile.ts
index 1bbaefcb4db01..66bbf6efc867b 100644
--- a/packages/playwright-core/src/tools/backend/logFile.ts
+++ b/packages/playwright-core/src/tools/backend/logFile.ts
@@ -91,7 +91,7 @@ export class LogFile {
return;
this._file ??= await this._context.outputFile({ prefix: this._filePrefix, ext: 'log', date: new Date(this._startTime) }, { origin: 'code' });
const relativeTime = Math.round(wallTime - this._startTime);
- const logLine = `[${String(relativeTime).padStart(8, ' ')}ms] ${text}\n`;
+ const logLine = `[${String(relativeTime).padStart(8, ' ')}ms] ${this._context.redactSecrets(text)}\n`;
await fs.promises.appendFile(this._file, logLine);
const lineCount = logLine.split('\n').length - 1;
diff --git a/packages/playwright-core/src/tools/backend/response.ts b/packages/playwright-core/src/tools/backend/response.ts
index db10fb696f5cd..388fb04d80673 100644
--- a/packages/playwright-core/src/tools/backend/response.ts
+++ b/packages/playwright-core/src/tools/backend/response.ts
@@ -111,7 +111,7 @@ export class Response {
private async _writeFile(resolvedFile: ResolvedFile, data: Buffer | string | null) {
if (typeof data === 'string')
- await fs.promises.writeFile(resolvedFile.fileName, this._redactSecrets(data), 'utf-8');
+ await fs.promises.writeFile(resolvedFile.fileName, this._context.redactSecrets(data), 'utf-8');
else if (data)
await fs.promises.writeFile(resolvedFile.fileName, data);
this._writtenFiles.add(path.resolve(resolvedFile.fileName));
@@ -155,16 +155,6 @@ export class Response {
this._includeSnapshotRoot = root;
}
- private _redactSecrets(text: string): string {
- for (const [secretName, secretValue] of Object.entries(this._context.config.secrets ?? {})) {
- if (!secretValue)
- continue;
- text = text.replaceAll(secretValue, `${secretName}`);
- }
- return text;
- }
-
-
async serialize(): Promise {
const allSections = await this._build();
await this._enforceOutputBudget();
@@ -211,7 +201,7 @@ export class Response {
const content: (TextContent | ImageContent)[] = [
{
type: 'text',
- text: sanitizeUnicode(this._redactSecrets(serializedText)),
+ text: sanitizeUnicode(this._context.redactSecrets(serializedText)),
}
];
diff --git a/tests/mcp/secrets.spec.ts b/tests/mcp/secrets.spec.ts
index eeae81a5bec5a..266c7f34ea376 100644
--- a/tests/mcp/secrets.spec.ts
+++ b/tests/mcp/secrets.spec.ts
@@ -16,7 +16,7 @@
import fs from 'node:fs';
-import { test, expect } from './fixtures';
+import { test, expect, parseResponse, consoleEntries } from './fixtures';
test('browser_type', async ({ startClient, server }) => {
const secretsFile = test.info().outputPath('secrets.env');
@@ -65,6 +65,43 @@ await page.getByRole('textbox').press('Enter');`,
});
+test('secrets are redacted in console log artifact', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41509' } }, async ({ startClient, server }) => {
+ const secretsFile = test.info().outputPath('secrets.env');
+ await fs.promises.writeFile(secretsFile, 'X-PASSWORD=password123');
+
+ const { client } = await startClient({
+ args: ['--secrets', secretsFile],
+ });
+
+ server.setContent('/', `
+
+
+
+
+ `, 'text/html');
+
+ await client.callTool({
+ name: 'browser_navigate',
+ arguments: {
+ url: server.PREFIX,
+ },
+ });
+
+ const response = parseResponse(await client.callTool({
+ name: 'browser_type',
+ arguments: {
+ element: 'textbox',
+ target: 'e2',
+ text: 'X-PASSWORD',
+ submit: true,
+ },
+ }));
+
+ const consoleLog = await consoleEntries(response);
+ expect(consoleLog).not.toContain('password123');
+ expect(consoleLog).toContain('X-PASSWORD');
+});
+
test('browser_fill_form', async ({ startClient, server }) => {
const secretsFile = test.info().outputPath('secrets.env');
await fs.promises.writeFile(secretsFile, 'X-PASSWORD=password123');