Skip to content

Commit 4271da6

Browse files
committed
Add BrowserStack Playwright demo
1 parent cd9e0b6 commit 4271da6

5 files changed

Lines changed: 407 additions & 0 deletions

File tree

.github/workflows/build.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,31 @@ jobs:
4343

4444
- name: Run tests
4545
run: npm run test -- --ci --coverage --maxWorkers=2 --reporters=default --reporters=github-actions
46+
47+
browserstack-playwright:
48+
name: BrowserStack Playwright Test
49+
runs-on: ubuntu-latest
50+
permissions:
51+
contents: read
52+
if: github.event_name == 'workflow_dispatch'
53+
env:
54+
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
55+
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
56+
57+
steps:
58+
- uses: actions/checkout@v4
59+
60+
- name: Setup Node.js
61+
uses: actions/setup-node@v3
62+
with:
63+
node-version: 20
64+
cache: npm
65+
66+
- name: Install dependencies
67+
run: npm ci
68+
69+
- name: Build packages
70+
run: npm run build
71+
72+
- name: Run BrowserStack Playwright test
73+
run: npm run test:browserstack
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { test, expect, chromium } from '@playwright/test';
2+
import { execSync } from 'child_process';
3+
import path from 'path';
4+
import { fileURLToPath } from 'url';
5+
import http from 'http';
6+
import handler from 'serve-handler';
7+
import BrowserStackLocal from 'browserstack-local';
8+
9+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
10+
const repoRoot = path.join(__dirname, '..');
11+
12+
let server;
13+
let bsLocal;
14+
15+
// Helper to start a local HTTP server
16+
function startHttpServer(port, rootDir) {
17+
return new Promise((resolve, reject) => {
18+
server = http.createServer((req, res) => {
19+
handler(req, res, {
20+
public: rootDir,
21+
cleanUrls: false,
22+
});
23+
});
24+
25+
server.listen(port, 'localhost', () => {
26+
console.log(`Server running on http://localhost:${port}`);
27+
resolve();
28+
});
29+
30+
server.on('error', reject);
31+
});
32+
}
33+
34+
// Helper to stop the HTTP server
35+
function stopHttpServer() {
36+
return new Promise((resolve, reject) => {
37+
if (server) {
38+
server.close((err) => {
39+
if (err) reject(err);
40+
else resolve();
41+
});
42+
} else {
43+
resolve();
44+
}
45+
});
46+
}
47+
48+
// Helper to start BrowserStack Local
49+
function startBrowserStackLocal(username, accessKey, localIdentifier) {
50+
return new Promise((resolve, reject) => {
51+
bsLocal = new BrowserStackLocal.Local();
52+
const bsLocalArgs = {
53+
key: accessKey,
54+
localIdentifier: localIdentifier,
55+
forceLocal: true,
56+
};
57+
58+
bsLocal.start(bsLocalArgs, (err) => {
59+
if (err) {
60+
reject(new Error(`Failed to start BrowserStack Local: ${err}`));
61+
} else {
62+
console.log('BrowserStack Local started');
63+
resolve();
64+
}
65+
});
66+
});
67+
}
68+
69+
// Helper to stop BrowserStack Local
70+
function stopBrowserStackLocal() {
71+
return new Promise((resolve) => {
72+
if (bsLocal && bsLocal.isRunning()) {
73+
bsLocal.stop(() => {
74+
console.log('BrowserStack Local stopped');
75+
resolve();
76+
});
77+
} else {
78+
resolve();
79+
}
80+
});
81+
}
82+
83+
test.describe('BrowserStack Playwright Demo', () => {
84+
const username = process.env.BROWSERSTACK_USERNAME;
85+
const accessKey = process.env.BROWSERSTACK_ACCESS_KEY;
86+
const localIdentifier = `local-${Date.now()}`;
87+
const localPort = 8080;
88+
const exampleDir = path.join(repoRoot, 'packages', 'plugin-button-click-counter');
89+
90+
test.beforeAll(async () => {
91+
if (!username || !accessKey) {
92+
throw new Error('BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY environment variables are required');
93+
}
94+
95+
// Start local HTTP server
96+
await startHttpServer(localPort, exampleDir);
97+
98+
// Start BrowserStack Local
99+
await startBrowserStackLocal(username, accessKey, localIdentifier);
100+
});
101+
102+
test.afterAll(async () => {
103+
// Stop BrowserStack Local
104+
await stopBrowserStackLocal();
105+
106+
// Stop HTTP server
107+
await stopHttpServer();
108+
});
109+
110+
test('should load example page and interact with button', async () => {
111+
const clientPlaywrightVersion = execSync('npx playwright --version')
112+
.toString()
113+
.trim()
114+
.split(' ')[1];
115+
116+
const capabilities = {
117+
browser: 'playwright-chromium',
118+
os: 'osx',
119+
os_version: 'sonoma',
120+
name: 'Plugin Button Click Counter Example',
121+
build: 'Training Repo Test',
122+
project: 'BrowserStack Playwright Demo',
123+
'browserstack.username': username,
124+
'browserstack.accessKey': accessKey,
125+
'browserstack.local': 'true',
126+
'browserstack.localIdentifier': localIdentifier,
127+
'client.playwrightVersion': clientPlaywrightVersion,
128+
};
129+
130+
const wsEndpoint = `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(
131+
JSON.stringify(capabilities)
132+
)}`;
133+
134+
console.log('BrowserStack capabilities:', {
135+
browser: capabilities.browser,
136+
os: capabilities.os,
137+
os_version: capabilities.os_version,
138+
name: capabilities.name,
139+
build: capabilities.build,
140+
project: capabilities.project,
141+
local: capabilities['browserstack.local'],
142+
localIdentifier: capabilities['browserstack.localIdentifier'],
143+
clientPlaywrightVersion: capabilities['client.playwrightVersion'],
144+
hasUsername: Boolean(capabilities['browserstack.username']),
145+
hasAccessKey: Boolean(capabilities['browserstack.accessKey']),
146+
});
147+
148+
const browser = await chromium.connect({
149+
wsEndpoint,
150+
});
151+
152+
const context = await browser.newContext();
153+
const page = await context.newPage();
154+
155+
try {
156+
await page.goto(`http://localhost:${localPort}/examples/index.html`, {
157+
waitUntil: 'networkidle',
158+
timeout: 30000,
159+
});
160+
161+
const body = page.locator('body');
162+
await expect(body).toBeVisible();
163+
164+
const buttons = page.locator('button');
165+
const buttonCount = await buttons.count();
166+
167+
if (buttonCount > 0) {
168+
const firstButton = buttons.first();
169+
await firstButton.click();
170+
console.log('Clicked the first button');
171+
}
172+
173+
await expect(body).toBeVisible();
174+
console.log('Test passed: page loaded and interacted successfully');
175+
} finally {
176+
await context.close();
177+
await browser.close();
178+
}
179+
});
180+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default {
2+
testDir: '.',
3+
testMatch: '**/browserstack-*.spec.js',
4+
fullyParallel: false,
5+
forbidOnly: !!process.env.CI,
6+
retries: process.env.CI ? 2 : 0,
7+
workers: 1,
8+
reporter: 'html',
9+
use: {
10+
trace: 'on-first-retry',
11+
screenshot: 'only-on-failure',
12+
},
13+
timeout: 60000,
14+
};

0 commit comments

Comments
 (0)