ワンクリックで
browserstack-cloud-testing
// Cloud-based cross-browser and cross-device testing with BrowserStack including Automate, App Automate, Percy visual testing, Observability, and integration with Playwright, Selenium, and CI/CD pipelines.
// Cloud-based cross-browser and cross-device testing with BrowserStack including Automate, App Automate, Percy visual testing, Observability, and integration with Playwright, Selenium, and CI/CD pipelines.
Pull weekly/monthly performance metrics for qaskills.sh from Google Analytics 4 (GA4) and Google Search Console (GSC) via Claude in Chrome. Returns traffic, channels, top queries, top pages, growth deltas in a single report.
AI-first testing methodology where autonomous agents plan, generate, execute, and maintain test suites with minimal human intervention, covering agent orchestration, feedback loops, and intelligent test prioritization.
Comprehensive evaluation patterns for AI agents including multi-turn conversation testing, LLM-as-judge frameworks, benchmark suites, regression detection, and systematic eval pipelines for measuring agent quality and safety.
Systematic patterns for prompting AI coding agents to generate high-quality tests including prompt engineering for test creation, coverage-driven generation, mutation-aware testing, and review checklists for AI-generated test code.
Comprehensive API security testing based on OWASP API Security Top 10 including broken authentication, injection attacks, rate limiting, BOLA/BFLA vulnerabilities, and automated security scanning with ZAP and custom scripts.
Fast test execution with Bun's built-in test runner including snapshot testing, mocking, code coverage, lifecycle hooks, DOM testing with happy-dom, and migration from Jest and Vitest to Bun test.
| name | BrowserStack Cloud Testing |
| description | Cloud-based cross-browser and cross-device testing with BrowserStack including Automate, App Automate, Percy visual testing, Observability, and integration with Playwright, Selenium, and CI/CD pipelines. |
| version | 1.0.0 |
| author | thetestingacademy |
| license | MIT |
| tags | ["browserstack","cross-browser","cloud-testing","device-testing","percy","visual-testing","automate","app-automate","observability","parallel-testing"] |
| testingTypes | ["e2e","visual","mobile","performance","accessibility"] |
| frameworks | ["playwright","selenium","cypress","appium"] |
| languages | ["typescript","javascript","java","python"] |
| domains | ["web","mobile"] |
| agents | ["claude-code","cursor","github-copilot","windsurf","codex","aider","continue","cline","zed","bolt","gemini-cli","amp"] |
You are an expert in BrowserStack cloud testing platform. When the user asks you to set up cross-browser testing, configure BrowserStack Automate with Playwright or Selenium, implement Percy visual testing, or optimize cloud test execution, follow these detailed instructions.
browserstack-tests/
config/
browserstack.config.ts
capabilities.ts
local-config.ts
tests/
e2e/
login.spec.ts
checkout.spec.ts
search.spec.ts
visual/
homepage-visual.spec.ts
product-page-visual.spec.ts
mobile/
mobile-navigation.spec.ts
mobile-checkout.spec.ts
helpers/
browserstack-client.ts
local-tunnel.ts
capability-builder.ts
percy/
percy-config.ts
snapshot-helpers.ts
scripts/
run-parallel.sh
check-session-status.ts
reports/
.gitkeep
// config/browserstack.config.ts
import { defineConfig, devices } from '@playwright/test';
const BS_CAPS = {
'browserstack.username': process.env.BROWSERSTACK_USERNAME || '',
'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY || '',
'browserstack.local': process.env.BROWSERSTACK_LOCAL || 'false',
'browserstack.playwrightVersion': '1.latest',
'browserstack.debug': 'true',
'browserstack.networkLogs': 'true',
'browserstack.consoleLogs': 'info',
};
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: 1,
workers: 5,
reporter: [
['html', { open: 'never' }],
['json', { outputFile: 'reports/results.json' }],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chrome-latest-windows',
use: {
connectOptions: {
wsEndpoint: buildWSEndpoint({
...BS_CAPS,
browser: 'chrome',
browser_version: 'latest',
os: 'Windows',
os_version: '11',
name: 'Chrome Latest Windows',
build: getBuildName(),
}),
},
},
},
{
name: 'firefox-latest-windows',
use: {
connectOptions: {
wsEndpoint: buildWSEndpoint({
...BS_CAPS,
browser: 'playwright-firefox',
browser_version: 'latest',
os: 'Windows',
os_version: '11',
name: 'Firefox Latest Windows',
build: getBuildName(),
}),
},
},
},
{
name: 'safari-latest-macos',
use: {
connectOptions: {
wsEndpoint: buildWSEndpoint({
...BS_CAPS,
browser: 'playwright-webkit',
browser_version: 'latest',
os: 'OS X',
os_version: 'Sonoma',
name: 'Safari Latest macOS',
build: getBuildName(),
}),
},
},
},
{
name: 'iphone-15',
use: {
connectOptions: {
wsEndpoint: buildWSEndpoint({
...BS_CAPS,
browser: 'playwright-webkit',
device: 'iPhone 15',
os_version: '17',
name: 'iPhone 15',
build: getBuildName(),
}),
},
},
},
{
name: 'pixel-8',
use: {
connectOptions: {
wsEndpoint: buildWSEndpoint({
...BS_CAPS,
browser: 'chrome',
device: 'Google Pixel 8',
os_version: '14.0',
name: 'Pixel 8',
build: getBuildName(),
}),
},
},
},
],
});
function buildWSEndpoint(caps: Record<string, string>): string {
const encoded = encodeURIComponent(JSON.stringify(caps));
return `wss://cdp.browserstack.com/playwright?caps=${encoded}`;
}
function getBuildName(): string {
const timestamp = new Date().toISOString().split('T')[0];
const ci = process.env.CI ? 'CI' : 'local';
const commit = process.env.GITHUB_SHA?.substring(0, 7) || 'dev';
return `${ci}-${timestamp}-${commit}`;
}
// helpers/local-tunnel.ts
import * as BrowserStackLocal from 'browserstack-local';
export class LocalTunnel {
private bsLocal: any;
constructor() {
this.bsLocal = new BrowserStackLocal.Local();
}
async start(options?: {
key?: string;
localIdentifier?: string;
verbose?: boolean;
force?: boolean;
forceLocal?: boolean;
}): Promise<void> {
return new Promise((resolve, reject) => {
const config = {
key: options?.key || process.env.BROWSERSTACK_ACCESS_KEY,
localIdentifier: options?.localIdentifier || `local-${Date.now()}`,
verbose: options?.verbose ? '1' : '0',
force: options?.force ? 'true' : 'false',
forceLocal: options?.forceLocal ? 'true' : 'false',
};
this.bsLocal.start(config, (error: any) => {
if (error) {
reject(new Error(`BrowserStack Local failed to start: ${error.message}`));
} else {
console.log('BrowserStack Local tunnel started');
resolve();
}
});
});
}
async stop(): Promise<void> {
return new Promise((resolve) => {
if (this.bsLocal.isRunning()) {
this.bsLocal.stop(() => {
console.log('BrowserStack Local tunnel stopped');
resolve();
});
} else {
resolve();
}
});
}
isRunning(): boolean {
return this.bsLocal.isRunning();
}
}
// tests/visual/homepage-visual.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Homepage Visual Regression', () => {
test('homepage renders correctly on desktop', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
// Take Percy snapshot
await page.evaluate(() => {
// Percy snapshot via CLI or SDK
(window as any).__percy_snapshot_name = 'Homepage - Desktop';
});
// Fallback assertions
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await expect(page.getByRole('navigation')).toBeVisible();
});
test('homepage renders correctly on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto('/');
await page.waitForLoadState('networkidle');
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
// Mobile hamburger menu should be visible
await expect(page.getByRole('button', { name: /menu/i })).toBeVisible();
});
});
// helpers/browserstack-client.ts
interface SessionInfo {
name: string;
duration: number;
os: string;
os_version: string;
browser: string;
browser_version: string;
status: 'passed' | 'failed' | 'error';
reason: string;
public_url: string;
video_url: string;
logs: string;
}
export class BrowserStackClient {
private username: string;
private accessKey: string;
private baseUrl = 'https://api.browserstack.com';
constructor() {
this.username = process.env.BROWSERSTACK_USERNAME || '';
this.accessKey = process.env.BROWSERSTACK_ACCESS_KEY || '';
}
private get authHeader(): string {
return `Basic ${Buffer.from(`${this.username}:${this.accessKey}`).toString('base64')}`;
}
async updateSessionStatus(
sessionId: string,
status: 'passed' | 'failed',
reason?: string
): Promise<void> {
await fetch(`${this.baseUrl}/automate/sessions/${sessionId}.json`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: this.authHeader,
},
body: JSON.stringify({ status, reason }),
});
}
async getSession(sessionId: string): Promise<SessionInfo> {
const response = await fetch(
`${this.baseUrl}/automate/sessions/${sessionId}.json`,
{ headers: { Authorization: this.authHeader } }
);
const data = await response.json();
return data.automation_session;
}
async getBuilds(limit = 10): Promise<any[]> {
const response = await fetch(
`${this.baseUrl}/automate/builds.json?limit=${limit}`,
{ headers: { Authorization: this.authHeader } }
);
const data = await response.json();
return data;
}
async getBuildSessions(buildId: string): Promise<any[]> {
const response = await fetch(
`${this.baseUrl}/automate/builds/${buildId}/sessions.json`,
{ headers: { Authorization: this.authHeader } }
);
return response.json();
}
async getAccountUsage(): Promise<{ parallel_sessions_running: number; parallel_sessions_max: number }> {
const response = await fetch(`${this.baseUrl}/automate/plan.json`, {
headers: { Authorization: this.authHeader },
});
return response.json();
}
}
# .github/workflows/browserstack-tests.yml
name: BrowserStack Cross-Browser Tests
on:
push:
branches: [main]
pull_request:
jobs:
browserstack:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Start BrowserStack Local
uses: browserstack/github-actions/setup-local@master
with:
local-testing: start
local-identifier: github-${{ github.run_id }}
- name: Run Playwright tests on BrowserStack
env:
BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
BROWSERSTACK_LOCAL: 'true'
BROWSERSTACK_LOCAL_IDENTIFIER: github-${{ github.run_id }}
run: npx playwright test --config=config/browserstack.config.ts
- name: Stop BrowserStack Local
if: always()
uses: browserstack/github-actions/setup-local@master
with:
local-testing: stop
- name: Upload Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: browserstack-reports
path: reports/
// helpers/capability-builder.ts
interface BrowserCapability {
browser: string;
browserVersion: string;
os: string;
osVersion: string;
deviceName?: string;
buildName: string;
sessionName: string;
}
export class CapabilityBuilder {
private baseCapabilities: Record<string, string> = {};
constructor() {
this.baseCapabilities = {
'browserstack.username': process.env.BROWSERSTACK_USERNAME || '',
'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY || '',
'browserstack.debug': 'true',
'browserstack.networkLogs': 'true',
'browserstack.consoleLogs': 'info',
};
}
desktop(browser: string, version: string, os: string, osVersion: string): Record<string, string> {
return {
...this.baseCapabilities,
browser,
browser_version: version,
os,
os_version: osVersion,
resolution: '1920x1080',
};
}
mobile(device: string, osVersion: string, browser = 'chrome'): Record<string, string> {
return {
...this.baseCapabilities,
device,
os_version: osVersion,
browser,
realMobile: 'true',
};
}
withLocal(identifier: string): this {
this.baseCapabilities['browserstack.local'] = 'true';
this.baseCapabilities['browserstack.localIdentifier'] = identifier;
return this;
}
withBuild(name: string): this {
this.baseCapabilities['build'] = name;
return this;
}
withGeolocation(country: string): this {
this.baseCapabilities['browserstack.geoLocation'] = country;
return this;
}
withNetworkProfile(profile: 'no-throttling' | '4g' | '3g' | '2g'): this {
const profiles: Record<string, string> = {
'no-throttling': 'no-throttling',
'4g': '4g-lte-advanced-good',
'3g': '3g-umts-good',
'2g': '2g-gprs-good',
};
this.baseCapabilities['browserstack.networkProfile'] = profiles[profile];
return this;
}
}
// Usage examples
const builder = new CapabilityBuilder()
.withBuild('PR-123-smoke')
.withLocal('github-12345');
const chromeWindows = builder.desktop('chrome', 'latest', 'Windows', '11');
const safariMac = builder.desktop('safari', 'latest', 'OS X', 'Sonoma');
const iPhone15 = builder.mobile('iPhone 15', '17');
const pixel8 = builder.mobile('Google Pixel 8', '14.0');
Not every test needs to run on every browser. Design a tiered execution strategy that maximizes coverage while minimizing cost and execution time.
Tier 1 runs on every commit and includes the smoke test suite on Chrome latest. This catches obvious regressions quickly.
Tier 2 runs on pull request merges and includes the full regression suite on Chrome, Firefox, and Safari. This catches cross-browser issues before code reaches the main branch.
Tier 3 runs nightly and includes the complete test suite on all configured browsers and devices, plus performance benchmarks. This catches subtle issues that only appear in specific browser or device configurations.
Tier 4 runs weekly and includes accessibility testing across all browsers, visual regression on key pages, and mobile-specific interaction testing on real devices.
Configure BrowserStack Observability to track test results across tiers. This provides a dashboard showing which browsers have the most failures, which tests are most likely to fail on specific browsers, and which device configurations need additional coverage.