-
-
Notifications
You must be signed in to change notification settings - Fork 10k
Addon-Vitest: Make Playwright --with-deps platform-aware to avoid sudo prompt on Linux
#34121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
05cf78f
388e33b
590dffb
8de539a
c9f38b0
bfb242e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import * as fs from 'node:fs/promises'; | ||
| import os from 'node:os'; | ||
|
|
||
| import { beforeEach, describe, expect, it, vi } from 'vitest'; | ||
|
|
||
|
|
@@ -14,6 +15,7 @@ import { SupportedBuilder, SupportedFramework } from '../types'; | |
| import { AddonVitestService } from './AddonVitestService'; | ||
|
|
||
| vi.mock('node:fs/promises', { spy: true }); | ||
| vi.mock('node:os', { spy: true }); | ||
| vi.mock('storybook/internal/common', { spy: true }); | ||
| vi.mock('storybook/internal/node-logger', { spy: true }); | ||
| vi.mock('empathic/find', { spy: true }); | ||
|
|
@@ -391,7 +393,7 @@ describe('AddonVitestService', () => { | |
| vi.mocked(logger.warn).mockImplementation(() => {}); | ||
| // Mock getPackageCommand to return a string | ||
| vi.mocked(mockPackageManager.getPackageCommand).mockReturnValue( | ||
| 'npx playwright install chromium --with-deps' | ||
| 'npx playwright install chromium' | ||
| ); | ||
| }); | ||
|
|
||
|
|
@@ -416,25 +418,127 @@ describe('AddonVitestService', () => { | |
| }); | ||
|
|
||
| it('should execute playwright install command', async () => { | ||
| type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise; | ||
| let commandFactory: ChildProcessFactory | ChildProcessFactory[]; | ||
| vi.mocked(prompt.confirm).mockResolvedValue(true); | ||
| vi.mocked(prompt.executeTaskWithSpinner).mockImplementation( | ||
| async (factory: ChildProcessFactory | ChildProcessFactory[]) => { | ||
| commandFactory = Array.isArray(factory) ? factory[0] : factory; | ||
| // Simulate the child process completion | ||
| commandFactory(); | ||
| const originalCI = process.env.CI; | ||
| delete process.env.CI; | ||
| vi.mocked(os.platform).mockReturnValue('linux'); | ||
| try { | ||
| type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise; | ||
| let commandFactory: ChildProcessFactory | ChildProcessFactory[]; | ||
| vi.mocked(prompt.confirm).mockResolvedValue(true); | ||
| vi.mocked(prompt.executeTaskWithSpinner).mockImplementation( | ||
| async (factory: ChildProcessFactory | ChildProcessFactory[]) => { | ||
| commandFactory = Array.isArray(factory) ? factory[0] : factory; | ||
| // Simulate the child process completion | ||
| commandFactory(); | ||
| } | ||
| ); | ||
|
Comment on lines
+428
to
+434
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move spinner mock implementations out of test cases and into
♻️ Suggested refactor describe('installPlaywright', () => {
+ type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
+
beforeEach(() => {
// Mock the logger methods used in installPlaywright
vi.mocked(logger.log).mockImplementation(() => {});
vi.mocked(logger.warn).mockImplementation(() => {});
// Mock getPackageCommand to return a string
vi.mocked(mockPackageManager.getPackageCommand).mockReturnValue(
'npx playwright install chromium'
);
+ vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
+ async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
+ const commandFactory = Array.isArray(factory) ? factory[0] : factory;
+ commandFactory();
+ return undefined;
+ }
+ );
});
it('should execute playwright install command', async () => {
const originalCI = process.env.CI;
delete process.env.CI;
try {
- type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
- let commandFactory: ChildProcessFactory | ChildProcessFactory[];
vi.mocked(prompt.confirm).mockResolvedValue(true);
- vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
- async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
- commandFactory = Array.isArray(factory) ? factory[0] : factory;
- // Simulate the child process completion
- commandFactory();
- }
- );
await service.installPlaywright();
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({
args: ['playwright', 'install', 'chromium'],
@@
it('should execute playwright install command with --with-deps in CI', async () => {
const originalCI = process.env.CI;
process.env.CI = 'true';
try {
- type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise;
- let commandFactory: ChildProcessFactory | ChildProcessFactory[];
vi.mocked(prompt.confirm).mockResolvedValue(true);
- vi.mocked(prompt.executeTaskWithSpinner).mockImplementation(
- async (factory: ChildProcessFactory | ChildProcessFactory[]) => {
- commandFactory = Array.isArray(factory) ? factory[0] : factory;
- commandFactory();
- }
- );
await service.installPlaywright();
expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({As per coding guidelines “Avoid inline mock implementations within test cases in Vitest tests” and “Implement mock behaviors in Also applies to: 454-459 🤖 Prompt for AI Agents |
||
|
|
||
| await service.installPlaywright(); | ||
|
|
||
| expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({ | ||
| args: ['playwright', 'install', 'chromium'], | ||
| signal: undefined, | ||
| stdio: ['inherit', 'pipe', 'pipe'], | ||
| }); | ||
| } finally { | ||
| if (originalCI !== undefined) { | ||
| process.env.CI = originalCI; | ||
| } | ||
| ); | ||
|
|
||
| await service.installPlaywright(); | ||
|
|
||
| expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({ | ||
| args: ['playwright', 'install', 'chromium', '--with-deps'], | ||
| signal: undefined, | ||
| stdio: ['inherit', 'pipe', 'pipe'], | ||
| }); | ||
| }); | ||
| } | ||
|
Comment on lines
+443
to
+447
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Environment variable restoration logic differs across tests. The
Lines 443-447 and 535-539 may leave 🛠️ Suggested fix for consistent cleanup } finally {
- if (originalCI !== undefined) {
- process.env.CI = originalCI;
+ if (originalCI === undefined) {
+ delete process.env.CI;
+ } else {
+ process.env.CI = originalCI;
}
}Apply this pattern to all three locations for consistency. Also applies to: 502-508, 535-539 🤖 Prompt for AI Agents |
||
| }); | ||
|
|
||
| it('should warn about missing system dependencies after install on Linux', async () => { | ||
| const originalCI = process.env.CI; | ||
| delete process.env.CI; | ||
| vi.mocked(os.platform).mockReturnValue('linux'); | ||
| try { | ||
| type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise; | ||
| vi.mocked(prompt.confirm).mockResolvedValue(true); | ||
| vi.mocked(prompt.executeTaskWithSpinner).mockImplementation( | ||
| async (factory: ChildProcessFactory | ChildProcessFactory[]) => { | ||
| const commandFactory = Array.isArray(factory) ? factory[0] : factory; | ||
| commandFactory(); | ||
| } | ||
| ); | ||
|
|
||
| const { result } = await service.installPlaywright(); | ||
|
|
||
| expect(result).toBe('installed'); | ||
| expect(logger.warn).toHaveBeenCalledWith( | ||
| expect.stringContaining('installed without system dependencies') | ||
| ); | ||
| expect(logger.warn).toHaveBeenCalledWith( | ||
| expect.stringContaining('run Storybook Test from the Storybook UI') | ||
| ); | ||
| } finally { | ||
| if (originalCI !== undefined) { | ||
| process.env.CI = originalCI; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| it('should execute playwright install command with --with-deps in CI', async () => { | ||
| const originalCI = process.env.CI; | ||
| process.env.CI = 'true'; | ||
| vi.mocked(os.platform).mockReturnValue('linux'); | ||
| try { | ||
| type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise; | ||
| let commandFactory: ChildProcessFactory | ChildProcessFactory[]; | ||
| vi.mocked(prompt.confirm).mockResolvedValue(true); | ||
| vi.mocked(prompt.executeTaskWithSpinner).mockImplementation( | ||
| async (factory: ChildProcessFactory | ChildProcessFactory[]) => { | ||
| commandFactory = Array.isArray(factory) ? factory[0] : factory; | ||
| commandFactory(); | ||
| } | ||
| ); | ||
|
|
||
| await service.installPlaywright(); | ||
|
|
||
| expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({ | ||
| args: ['playwright', 'install', 'chromium', '--with-deps'], | ||
| signal: undefined, | ||
| stdio: ['inherit', 'pipe', 'pipe'], | ||
| }); | ||
| } finally { | ||
| if (originalCI === undefined) { | ||
| delete process.env.CI; | ||
| } else { | ||
| process.env.CI = originalCI; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| it.each(['darwin', 'win32'] as const)( | ||
| 'should execute playwright install command with --with-deps on %s', | ||
| async (platform) => { | ||
| const originalCI = process.env.CI; | ||
| delete process.env.CI; | ||
| vi.mocked(os.platform).mockReturnValue(platform); | ||
| try { | ||
| type ChildProcessFactory = (signal?: AbortSignal) => ResultPromise; | ||
| let commandFactory: ChildProcessFactory | ChildProcessFactory[]; | ||
| vi.mocked(prompt.confirm).mockResolvedValue(true); | ||
| vi.mocked(prompt.executeTaskWithSpinner).mockImplementation( | ||
| async (factory: ChildProcessFactory | ChildProcessFactory[]) => { | ||
| commandFactory = Array.isArray(factory) ? factory[0] : factory; | ||
| commandFactory(); | ||
| } | ||
| ); | ||
|
|
||
| await service.installPlaywright(); | ||
|
|
||
| expect(mockPackageManager.runPackageCommand).toHaveBeenCalledWith({ | ||
| args: ['playwright', 'install', 'chromium', '--with-deps'], | ||
| signal: undefined, | ||
| stdio: ['inherit', 'pipe', 'pipe'], | ||
| }); | ||
| } finally { | ||
| if (originalCI !== undefined) { | ||
| process.env.CI = originalCI; | ||
| } | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| it('should capture error stack when installation fails', async () => { | ||
| const error = new Error('Installation failed'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import fs from 'node:fs/promises'; | ||
| import os from 'node:os'; | ||
|
|
||
| import * as babel from 'storybook/internal/babel'; | ||
| import type { JsPackageManager } from 'storybook/internal/common'; | ||
|
|
@@ -106,7 +107,10 @@ export class AddonVitestService { | |
| /** | ||
| * Install Playwright browser binaries for @storybook/addon-vitest | ||
| * | ||
| * Installs Chromium with dependencies via `npx playwright install chromium --with-deps` | ||
| * Installs Chromium via `npx playwright install chromium`. In CI environments and on | ||
| * macOS/Windows (officially supported platforms), also installs system-level browser dependencies | ||
| * via `--with-deps`. On other platforms (e.g. Linux), `--with-deps` is omitted to avoid requiring | ||
| * `sudo` — system packages are typically managed by the distro package manager. | ||
| * | ||
| * @param packageManager - The package manager to use for installation | ||
| * @param prompt - The prompt instance for displaying progress | ||
|
|
@@ -123,7 +127,11 @@ export class AddonVitestService { | |
| ): Promise<{ errors: string[]; result: 'installed' | 'skipped' | 'aborted' | 'failed' }> { | ||
| const errors: string[] = []; | ||
|
|
||
| const playwrightCommand = ['playwright', 'install', 'chromium', '--with-deps']; | ||
| const platform = os.platform(); | ||
| const useWithDeps = !!process.env.CI || platform === 'darwin' || platform === 'win32'; | ||
| const playwrightCommand = useWithDeps | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! @copilot now, when not using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in c9f38b0. After a successful install without |
||
| ? ['playwright', 'install', 'chromium', '--with-deps'] | ||
| : ['playwright', 'install', 'chromium']; | ||
| const playwrightCommandString = this.packageManager.getPackageCommand(playwrightCommand); | ||
|
|
||
| let result: 'installed' | 'skipped' | 'aborted' | 'failed'; | ||
|
|
@@ -168,6 +176,14 @@ export class AddonVitestService { | |
| result = 'aborted'; | ||
| } else { | ||
| result = 'installed'; | ||
| if (!useWithDeps) { | ||
| logger.warn(dedent` | ||
| Playwright was installed without system dependencies. Depending on your operating system, you may need to install additional libraries for Playwright to work correctly. | ||
| To check for missing dependencies, run Storybook Test from the Storybook UI — it will report any libraries that need to be installed. | ||
| On MacOS, Windows, Debian and Ubuntu, you can install system dependencies manually by running: | ||
| ${CLI_COLORS.cta(this.packageManager.getPackageCommand(['playwright', 'install', 'chromium', '--with-deps']))} | ||
|
Comment on lines
+181
to
+184
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @valentinpalkovic WDYT of this? I would only change "If needed" with "On MacOS, Windows, Debian and Ubuntu," |
||
| `); | ||
| } | ||
| } | ||
| } else { | ||
| logger.warn('Playwright installation skipped'); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @Sidnioulz: Isn't
win32a check for Windows? Do they need to run the command with--with-deps?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@valentinpalkovic we know that the --with-deps command has worked well so far for MacOS and Windows users, so there is no reason to risk degrading their experience. We know all other OSes are partially or totally unsupported though.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand your thinking now. I've seen it as an opportunity to fasten up the init step by a bit. But fair enough. Let's go the safe route first.