Skip to content
Open
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
8 changes: 7 additions & 1 deletion packages/playwright-core/src/server/firefox/firefox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ManualPromise } from '@isomorphic/manualPromise';
import { wrapInASCIIBox } from '@utils/ascii';
import { FFBrowser } from './ffBrowser';
import { kBrowserCloseMessageId } from './ffConnection';
import { amendFirefoxPoliciesEnv, prepareFirefoxPolicies } from './firefoxPolicies';
import { BrowserType, kNoXServerRunningError } from '../browserType';

import type { Browser, BrowserOptions } from '../browser';
Expand Down Expand Up @@ -57,6 +58,10 @@ export class Firefox extends BrowserType {
return FFBrowser.connect(this.attribution.playwright, transport, options);
}

override async prepareUserDataDir(options: types.LaunchOptions, userDataDir: string): Promise<void> {
await prepareFirefoxPolicies(options, userDataDir);
}

override doRewriteStartupLog(logs: string): string {
// https://github.com/microsoft/playwright/issues/6500
if (logs.includes(`as root in a regular user's session is not supported.`))
Expand All @@ -66,7 +71,8 @@ export class Firefox extends BrowserType {
return logs;
}

override amendEnvironment(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
override amendEnvironment(env: NodeJS.ProcessEnv, userDataDir: string): NodeJS.ProcessEnv {
env = amendFirefoxPoliciesEnv(env, userDataDir);
if (!path.isAbsolute(os.homedir()))
throw new Error(`Cannot launch Firefox with relative home directory. Did you set ${os.platform() === 'win32' ? 'USERPROFILE' : 'HOME'} to a relative path?`);
if (os.platform() === 'linux') {
Expand Down
66 changes: 66 additions & 0 deletions packages/playwright-core/src/server/firefox/firefoxPolicies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import fs from 'fs';
import path from 'path';

import { envArrayToObject } from '@utils/processLauncher';

export const kFirefoxPoliciesEnvName = 'PLAYWRIGHT_FIREFOX_POLICIES_JSON';

type FirefoxPoliciesOptions = {
env?: { name: string, value: string }[];
};

type FirefoxPolicies = {
policies?: Record<string, unknown>;
[key: string]: unknown;
};

export function firefoxPoliciesPath(userDataDir: string): string {
return path.join(userDataDir, 'playwright-policies.json');
}

export async function prepareFirefoxPolicies(options: FirefoxPoliciesOptions, userDataDir: string): Promise<void> {
const env = options.env ? envArrayToObject(options.env) : process.env;
const userPolicies = await readFirefoxPolicies(env[kFirefoxPoliciesEnvName]);
const policies: FirefoxPolicies = {
...userPolicies,
policies: {
...userPolicies.policies,
// Keep Playwright-managed Firefox builds from starting their own update service.
DisableAppUpdate: true,
},
};
await fs.promises.writeFile(firefoxPoliciesPath(userDataDir), JSON.stringify(policies));
}

export function amendFirefoxPoliciesEnv(env: NodeJS.ProcessEnv, userDataDir: string): NodeJS.ProcessEnv {
return {
...env,
[kFirefoxPoliciesEnvName]: firefoxPoliciesPath(userDataDir),
};
}

async function readFirefoxPolicies(policiesPath: string | undefined): Promise<FirefoxPolicies> {
if (!policiesPath)
return {};
try {
return JSON.parse(await fs.promises.readFile(policiesPath, 'utf8'));
} catch (error) {
throw new Error(`Failed to read Firefox policies from ${policiesPath}: ${error}`);
}
}
84 changes: 84 additions & 0 deletions tests/library/firefox/launcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*/

import fs from 'fs';
import path from 'path';
import { playwrightTest as it, expect } from '../../config/browserTest';
import { TestServer } from '../../config/testserver';
import { inheritAndCleanEnv } from '../../config/utils';
import { amendFirefoxPoliciesEnv, kFirefoxPoliciesEnvName, prepareFirefoxPolicies } from '../../../packages/playwright-core/src/server/firefox/firefoxPolicies';

it('should pass firefox user preferences', async ({ browserType, mode }) => {
const browser = await browserType.launch({
Expand Down Expand Up @@ -73,3 +75,85 @@ it('should support custom firefox policies', async ({ browserType, mode, asset,
await browser.close();
await server.stop();
});

it('should launch with generated firefox policies file', async ({ browserType, createUserDataDir }, testInfo) => {
const policiesPath = testInfo.outputPath('policies.json');
await fs.promises.writeFile(policiesPath, JSON.stringify({
policies: {
DisableTelemetry: true,
},
}));

const userDataDir = await createUserDataDir();
const context = await browserType.launchPersistentContext(userDataDir, {
env: inheritAndCleanEnv({ [kFirefoxPoliciesEnvName]: policiesPath }),
});
const generatedPolicies = JSON.parse(await fs.promises.readFile(path.join(userDataDir, 'playwright-policies.json'), 'utf8'));
expect(generatedPolicies).toEqual({
policies: {
DisableTelemetry: true,
DisableAppUpdate: true,
},
});
await context.close();
});

it('should merge custom firefox policies with disabled app updates', async ({}, testInfo) => {
const defaultUserDataDir = testInfo.outputPath('default-profile');
await fs.promises.mkdir(defaultUserDataDir);
await prepareFirefoxPolicies({}, defaultUserDataDir);
expect(JSON.parse(await fs.promises.readFile(path.join(defaultUserDataDir, 'playwright-policies.json'), 'utf8'))).toEqual({
policies: {
DisableAppUpdate: true,
},
});

const policiesPath = testInfo.outputPath('policies.json');
await fs.promises.writeFile(policiesPath, JSON.stringify({
policies: {
Certificates: {
Install: ['cert.pem'],
},
DisableAppUpdate: false,
},
}));

const userDataDir = testInfo.outputPath('profile');
await fs.promises.mkdir(userDataDir);
await prepareFirefoxPolicies({
env: [{ name: kFirefoxPoliciesEnvName, value: policiesPath }],
}, userDataDir);

const generatedPolicies = JSON.parse(await fs.promises.readFile(path.join(userDataDir, 'playwright-policies.json'), 'utf8'));
expect(generatedPolicies).toEqual({
policies: {
Certificates: {
Install: ['cert.pem'],
},
DisableAppUpdate: true,
},
});
});

it('should point Firefox to generated policies file', async ({}, testInfo) => {
const userDataDir = testInfo.outputPath('profile');
expect(amendFirefoxPoliciesEnv({}, userDataDir)).toEqual({
[kFirefoxPoliciesEnvName]: path.join(userDataDir, 'playwright-policies.json'),
});
});

it('should report invalid custom firefox policies', async ({}, testInfo) => {
const userDataDir = testInfo.outputPath('profile');
await fs.promises.mkdir(userDataDir);

const missingPoliciesPath = testInfo.outputPath('missing-policies.json');
await expect(prepareFirefoxPolicies({
env: [{ name: kFirefoxPoliciesEnvName, value: missingPoliciesPath }],
}, userDataDir)).rejects.toThrow(`Failed to read Firefox policies from ${missingPoliciesPath}`);

const invalidPoliciesPath = testInfo.outputPath('invalid-policies.json');
await fs.promises.writeFile(invalidPoliciesPath, '{');
await expect(prepareFirefoxPolicies({
env: [{ name: kFirefoxPoliciesEnvName, value: invalidPoliciesPath }],
}, userDataDir)).rejects.toThrow(`Failed to read Firefox policies from ${invalidPoliciesPath}`);
});