any-browser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/bin/cli.js +43 -0
- package/package.json +53 -0
- package/playwright.config.js +23 -0
- package/src/browsers.js +39 -0
- package/src/postinstall.js +36 -0
- package/src/runner.js +59 -0
- package/tests/browser.test.ts +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 BhalliBhai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# any-browser
|
|
2
|
+
|
|
3
|
+
Test your website on Chrome, Firefox, and Safari — from any OS — with one command.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
any-browser test safari
|
|
7
|
+
any-browser test chrome firefox
|
|
8
|
+
any-browser test all
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Getting Started](#getting-started)
|
|
14
|
+
- [Usage](#usage)
|
|
15
|
+
- [What Are The Major Browsers?](#what-are-the-major-browsers)
|
|
16
|
+
- [Running From Source Instead](#running-from-source-instead)
|
|
17
|
+
- [Troubleshooting](#troubleshooting)
|
|
18
|
+
- [Publishing / Releasing](#publishing--releasing) (maintainers)
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
Install it globally from npm:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npm install -g any-browser
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The first install downloads the Chrome, Firefox, and Safari (WebKit) engines
|
|
29
|
+
via Playwright — that's a one-time ~300MB download, and you'll see exactly
|
|
30
|
+
what's happening and why in your terminal. After that, every run is instant.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
any-browser test <browser...> [options]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Browser argument | Engine used |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `chrome` | Chromium |
|
|
41
|
+
| `firefox` | Firefox |
|
|
42
|
+
| `safari` | WebKit |
|
|
43
|
+
| `all` | all three |
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
|
|
47
|
+
- `-u, --url <url>` — URL to test (default: `http://localhost:3000`)
|
|
48
|
+
- `--headless` — run without opening a visible window
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
any-browser test safari
|
|
54
|
+
any-browser test chrome firefox
|
|
55
|
+
any-browser test all --url https://example.com
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
🎇 Each browser opens in front of you, paused, so you can click around and
|
|
59
|
+
inspect the page exactly like a real visitor would.
|
|
60
|
+
|
|
61
|
+
## What Are The Major Browsers?
|
|
62
|
+
|
|
63
|
+
Ensuring your website looks great across all major browsers is crucial for a
|
|
64
|
+
consistent user experience. `any-browser` covers Chrome, Firefox, and Safari
|
|
65
|
+
regardless of your operating system — Linux, macOS, or Windows.
|
|
66
|
+
|
|
67
|
+
If we look at browser market share worldwide, Chrome dominates at around 64%,
|
|
68
|
+
Safari follows with about 20%, and Firefox holds roughly 4%. While there are
|
|
69
|
+
many other browsers, these three are critical because they use distinct
|
|
70
|
+
rendering engines:
|
|
71
|
+
|
|
72
|
+
- **Chrome** (and Chromium-based browsers like Edge and Brave) use **Blink**.
|
|
73
|
+
- **Firefox** uses **Gecko**, developed by Mozilla.
|
|
74
|
+
- **Safari** uses **WebKit**, developed by Apple.
|
|
75
|
+
|
|
76
|
+
Since Safari is exclusive to macOS, testing it from Linux or Windows normally
|
|
77
|
+
requires workarounds. `any-browser` sidesteps that entirely by using
|
|
78
|
+
Playwright's open-source WebKit build, so `any-browser test safari` works
|
|
79
|
+
identically on any OS.
|
|
80
|
+
|
|
81
|
+
## Running From Source Instead
|
|
82
|
+
|
|
83
|
+
Prefer to clone and run it locally rather than install globally? That still
|
|
84
|
+
works exactly like before:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
git clone https://github.com/BhalliBhai/any-browser-cli.git
|
|
88
|
+
cd any-browser-cli
|
|
89
|
+
npm install
|
|
90
|
+
npm run test:safari # or test:chrome / test:firefox
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Troubleshooting
|
|
94
|
+
|
|
95
|
+
If you hit missing dependencies on Linux when running WebKit tests:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
npx playwright install-deps webkit
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If you see an error about missing libraries such as:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
browserType.launch:
|
|
105
|
+
Host system is missing dependencies to run browsers.
|
|
106
|
+
Missing libraries:
|
|
107
|
+
libpcre.so.3
|
|
108
|
+
libicui18n.so.66
|
|
109
|
+
libicuuc.so.66
|
|
110
|
+
libwebp.so.6
|
|
111
|
+
libenchant.so.1
|
|
112
|
+
libffi.so.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Install the necessary system packages. On Ubuntu/Debian:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
sudo apt-get install -y libpcre3 libicu66 libwebp6 libenchant1c2a libffi7
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For other distributions, check your package manager's docs, or just run
|
|
122
|
+
`npx playwright install-deps` and let Playwright figure it out.
|
|
123
|
+
|
|
124
|
+
## Publishing / Releasing
|
|
125
|
+
|
|
126
|
+
See [PUBLISHING.md](./PUBLISHING.md) for the one-time npm/GitHub setup and the
|
|
127
|
+
release workflow.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
If this helped, consider starring ⭐ the repository to support the project! 😊
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { runTests } from '../src/runner.js';
|
|
7
|
+
import { SUPPORTED_BROWSERS } from '../src/browsers.js';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name('any-browser')
|
|
16
|
+
.description('Test your website across Chrome, Firefox, and Safari from one CLI command.')
|
|
17
|
+
.version(pkg.version, '-v, --version');
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command('test')
|
|
21
|
+
.description('Open your site in one or more browsers for testing')
|
|
22
|
+
.argument(
|
|
23
|
+
'<browsers...>',
|
|
24
|
+
`browser(s) to test — ${Object.keys(SUPPORTED_BROWSERS).join(', ')}, or "all"`
|
|
25
|
+
)
|
|
26
|
+
.option('-u, --url <url>', 'URL to test', 'http://localhost:3000')
|
|
27
|
+
.option('--headless', 'run without opening a visible browser window', false)
|
|
28
|
+
.action(async (browsers, options) => {
|
|
29
|
+
await runTests(browsers, options);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
program.addHelpText(
|
|
33
|
+
'after',
|
|
34
|
+
`
|
|
35
|
+
Examples:
|
|
36
|
+
$ any-browser test safari
|
|
37
|
+
$ any-browser test chrome firefox
|
|
38
|
+
$ any-browser test all
|
|
39
|
+
$ any-browser test chrome --url https://example.com
|
|
40
|
+
`
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "any-browser",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Test your website across Chrome, Firefox, and Safari (WebKit) with one CLI command.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"any-browser": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/runner.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"playwright.config.js",
|
|
14
|
+
"tests",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"postinstall": "node src/postinstall.js",
|
|
20
|
+
"test:chrome": "playwright test --project=chromium --headed",
|
|
21
|
+
"test:firefox": "playwright test --project=firefox --headed",
|
|
22
|
+
"test:safari": "playwright test --project=webkit --headed"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cli",
|
|
26
|
+
"browser-testing",
|
|
27
|
+
"playwright",
|
|
28
|
+
"cross-browser",
|
|
29
|
+
"chrome",
|
|
30
|
+
"firefox",
|
|
31
|
+
"safari",
|
|
32
|
+
"webkit",
|
|
33
|
+
"qa"
|
|
34
|
+
],
|
|
35
|
+
"author": "BhalliBhai (https://bhalli.dev)",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/BhalliBhai/any-browser-cli.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/BhalliBhai/any-browser-cli#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/BhalliBhai/any-browser-cli/issues"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@playwright/test": "^1.50.0",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"picocolors": "^1.1.1"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
// Device presets bundle a fixed viewport + deviceScaleFactor together.
|
|
4
|
+
// We want the real, resizable window instead, so strip both before reapplying.
|
|
5
|
+
function desktop(deviceName) {
|
|
6
|
+
const { viewport, deviceScaleFactor, ...rest } = devices[deviceName];
|
|
7
|
+
return rest;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
testDir: './tests',
|
|
12
|
+
fullyParallel: false,
|
|
13
|
+
reporter: 'list',
|
|
14
|
+
timeout: 0, // no timeout — these tests wait for you to close the window
|
|
15
|
+
use: {
|
|
16
|
+
trace: 'off',
|
|
17
|
+
},
|
|
18
|
+
projects: [
|
|
19
|
+
{ name: 'chromium', use: { ...desktop('Desktop Chrome'), viewport: null } },
|
|
20
|
+
{ name: 'firefox', use: { ...desktop('Desktop Firefox'), viewport: null } },
|
|
21
|
+
{ name: 'webkit', use: { ...desktop('Desktop Safari'), viewport: null } },
|
|
22
|
+
],
|
|
23
|
+
});
|
package/src/browsers.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const SUPPORTED_BROWSERS = {
|
|
2
|
+
chrome: 'chromium',
|
|
3
|
+
chromium: 'chromium',
|
|
4
|
+
firefox: 'firefox',
|
|
5
|
+
safari: 'webkit',
|
|
6
|
+
webkit: 'webkit',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const ENGINE_LABELS = {
|
|
10
|
+
chromium: 'Chrome',
|
|
11
|
+
firefox: 'Firefox',
|
|
12
|
+
webkit: 'Safari',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Turns whatever the user typed (["safari", "Chrome", "all"]) into a clean,
|
|
17
|
+
* deduplicated list of Playwright engine names, plus anything we didn't recognize.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveBrowsers(requested) {
|
|
20
|
+
const list = requested.map((b) => b.toLowerCase());
|
|
21
|
+
|
|
22
|
+
if (list.includes('all')) {
|
|
23
|
+
return { resolved: ['chromium', 'firefox', 'webkit'], unknown: [] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const resolved = [];
|
|
27
|
+
const unknown = [];
|
|
28
|
+
|
|
29
|
+
for (const name of list) {
|
|
30
|
+
const engine = SUPPORTED_BROWSERS[name];
|
|
31
|
+
if (!engine) {
|
|
32
|
+
unknown.push(name);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!resolved.includes(engine)) resolved.push(engine);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { resolved, unknown };
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
|
|
5
|
+
const isCI = !!process.env.CI;
|
|
6
|
+
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log(pc.cyan('any-browser needs real browser engines (Chromium, Firefox, WebKit) to run tests.'));
|
|
9
|
+
console.log(
|
|
10
|
+
pc.dim('This is a one-time download (~300MB total, handled by Playwright) so every')
|
|
11
|
+
);
|
|
12
|
+
console.log(pc.dim('"any-browser test <browser>" run afterwards is instant — no setup needed.'));
|
|
13
|
+
|
|
14
|
+
if (isCI) {
|
|
15
|
+
console.log(pc.yellow('\nCI environment detected — skipping the automatic download.'));
|
|
16
|
+
console.log(pc.dim('Add this as its own step in your CI pipeline instead:'));
|
|
17
|
+
console.log(pc.bold(' npx playwright install --with-deps\n'));
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(pc.dim('\nDownloading now...\n'));
|
|
22
|
+
|
|
23
|
+
const result = spawnSync('npx', ['playwright', 'install', 'chromium', 'firefox', 'webkit'], {
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
shell: process.platform === 'win32',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (result.status === 0) {
|
|
29
|
+
console.log(pc.green("\n✓ Browser engines installed. You're ready to go:"));
|
|
30
|
+
console.log(pc.bold(' any-browser test chrome firefox safari\n'));
|
|
31
|
+
} else {
|
|
32
|
+
console.log(pc.yellow("\nCouldn't auto-install the browser engines (this can happen behind"));
|
|
33
|
+
console.log(pc.yellow('certain firewalls/proxies, or if disk space is low).'));
|
|
34
|
+
console.log(pc.dim('Run this once manually, then you\'re good:'));
|
|
35
|
+
console.log(pc.bold(' npx playwright install chromium firefox webkit\n'));
|
|
36
|
+
}
|
package/src/runner.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { resolveBrowsers, ENGINE_LABELS } from './browsers.js';
|
|
4
|
+
|
|
5
|
+
export async function runTests(browserArgs, options) {
|
|
6
|
+
const { resolved, unknown } = resolveBrowsers(browserArgs);
|
|
7
|
+
|
|
8
|
+
if (unknown.length) {
|
|
9
|
+
console.log(pc.yellow(`Skipping unknown browser(s): ${unknown.join(', ')}`));
|
|
10
|
+
console.log(pc.dim('Supported: chrome, firefox, safari, all'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!resolved.length) {
|
|
14
|
+
console.log(pc.red('No valid browsers to test. Try: any-browser test chrome firefox safari'));
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const names = resolved.map((e) => ENGINE_LABELS[e]).join(', ');
|
|
20
|
+
console.log(pc.cyan(`\nTesting ${options.url} in: ${names}\n`));
|
|
21
|
+
|
|
22
|
+
for (const engine of resolved) {
|
|
23
|
+
// eslint-disable-next-line no-await-in-loop -- browsers run one at a time on purpose
|
|
24
|
+
await runOne(engine, options);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function runOne(engine, options) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
console.log(pc.bold(`\n▶ ${ENGINE_LABELS[engine]} (${engine})`));
|
|
31
|
+
|
|
32
|
+
const args = ['playwright', 'test', `--project=${engine}`];
|
|
33
|
+
if (!options.headless) args.push('--headed');
|
|
34
|
+
|
|
35
|
+
const child = spawn('npx', args, {
|
|
36
|
+
stdio: 'inherit',
|
|
37
|
+
env: {
|
|
38
|
+
...process.env,
|
|
39
|
+
TEST_URL: options.url,
|
|
40
|
+
TEST_HEADLESS: options.headless ? '1' : '0',
|
|
41
|
+
},
|
|
42
|
+
shell: process.platform === 'win32',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('close', (code) => {
|
|
46
|
+
if (code === 0) {
|
|
47
|
+
console.log(pc.green(`✓ ${ENGINE_LABELS[engine]} finished`));
|
|
48
|
+
} else {
|
|
49
|
+
console.log(pc.red(`✗ ${ENGINE_LABELS[engine]} exited with code ${code}`));
|
|
50
|
+
}
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
child.on('error', (err) => {
|
|
55
|
+
console.log(pc.red(`✗ Couldn't start ${ENGINE_LABELS[engine]}: ${err.message}`));
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { test } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test('open site for manual testing', async ({ page }) => {
|
|
4
|
+
const url = process.env.TEST_URL || 'http://localhost:3000';
|
|
5
|
+
const headless = process.env.TEST_HEADLESS === '1';
|
|
6
|
+
|
|
7
|
+
await page.goto(url);
|
|
8
|
+
|
|
9
|
+
if (headless) {
|
|
10
|
+
// No window for a human to close in headless mode — just confirm the
|
|
11
|
+
// page settled, then move on to the next browser.
|
|
12
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log('\n Window open — close it when you\'re done to move to the next browser.\n');
|
|
17
|
+
|
|
18
|
+
// No Inspector, no pause button to hunt for — closing the window is the signal.
|
|
19
|
+
await page.waitForEvent('close', { timeout: 0 }).catch(() => {});
|
|
20
|
+
});
|