Install
$ agentstack add skill-pekral-cursor-rules-e2e-testing ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Constraints
- Apply
@rules/code-testing/general.mdcfor the deterministic-test contract: tests must be reliable, isolated, and free of arbitrary sleeps. - GATED skill: proceed only when the consuming project already ships Playwright. Never install Playwright unprompted.
- Stable selectors over brittle ones: prefer role and
data-testidover CSS/structure. - No arbitrary
waitForTimeout. Rely on auto-waiting locators and explicit conditions.
Use when
- Writing Playwright E2E browser tests for a Laravel app.
- Stabilizing flaky Playwright tests.
- Wiring Playwright into CI against the app under test.
Preconditions (run first, do not skip)
Check whether Playwright is actually present:
ls playwright.config.* 2>/dev/null
grep -q '@playwright/test' package.json 2>/dev/null && echo "playwright present"
- If neither a
playwright.config.*file nor@playwright/testinpackage.jsonexists, STOP. Do not install Playwright. Defer to: @skills/test-like-human/SKILL.mdfor manual, scenario-based testing, or- Pest feature tests / Laravel Dusk for browser coverage inside the PHP stack.
- Only when Playwright IS present, continue with the patterns below.
File organization
tests/
e2e/
auth/login.spec.ts
features/search.spec.ts
pages/
ItemsPage.ts
fixtures/
auth.ts
playwright.config.ts
Page Object Model
Encapsulate page structure so specs read as behavior, not selectors.
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
constructor(page: Page) {
this.page = page
// Prefer role/test-id selectors. data-testid is stable across restyles.
this.searchInput = page.getByTestId('search-input')
this.itemCards = page.getByTestId('item-card')
this.createButton = page.getByRole('button', { name: 'Create' })
}
async goto() {
await this.page.goto('/items')
}
async search(query: string) {
await this.searchInput.fill(query)
// Wait for the real response, not a fixed delay.
await this.page.waitForResponse(r => r.url().includes('/items') && r.ok())
}
itemCount() {
return this.itemCards.count()
}
}
Stable selectors
Order of preference:
getByRole(...)with an accessible name.getByTestId(...)— adddata-testidto the relevant Blade/Livewire markup.getByLabel/getByTextfor forms and copy.- Raw CSS only as a last resort; it breaks on Tailwind restyles.
Test structure
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../pages/ItemsPage'
test.describe('Item search', () => {
let items: ItemsPage
test.beforeEach(async ({ page }) => {
items = new ItemsPage(page)
await items.goto()
})
test('searches by keyword', async () => {
await items.search('test')
expect(await items.itemCount()).toBeGreaterThan(0)
await expect(items.itemCards.first()).toContainText(/test/i)
})
test('shows empty state with no results', async ({ page }) => {
await items.search('zzz-nonexistent')
await expect(page.getByTestId('no-results')).toBeVisible()
expect(await items.itemCount()).toBe(0)
})
})
Running against the Laravel app
Point Playwright at the running app and let it boot php artisan serve (or use an already-running instance / CI app URL).
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
reporter: [['html', { outputFolder: 'playwright-report' }], ['junit', { outputFile: 'playwright-results.xml' }]],
use: {
baseURL: process.env.APP_URL ?? 'http://127.0.0.1:8000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'php artisan serve --port=8000',
url: 'http://127.0.0.1:8000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
})
Use a dedicated testing database/env (.env.testing) so E2E runs never touch development data, and migrate/seed it before the suite.
Auth and network state reuse
Log in once, save storage state, and reuse it so specs skip the login flow.
// global-setup.ts — sign in once, persist cookies/session
import { chromium } from '@playwright/test'
export default async () => {
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto(`${process.env.APP_URL ?? 'http://127.0.0.1:8000'}/login`)
await page.getByLabel('Email').fill(process.env.E2E_EMAIL!)
await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!)
await page.getByRole('button', { name: 'Log in' }).click()
await page.waitForURL('**/dashboard')
await page.context().storageState({ path: 'tests/e2e/.auth/user.json' })
await browser.close()
}
Wire it via globalSetup and use: { storageState: 'tests/e2e/.auth/user.json' }. Keep .auth/ out of version control. You can also stub backend responses with page.route(...) to isolate the frontend from flaky external services.
CI wiring
name: E2E
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with: { php-version: '8.3' }
- run: composer install --no-interaction --prefer-dist
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: php artisan migrate --seed --env=testing
- run: npx playwright test
env:
APP_URL: http://127.0.0.1:8000
- uses: actions/upload-artifact@v4
if: always()
with: { name: playwright-report, path: playwright-report/, retention-days: 30 }
Flaky-test avoidance
Per @rules/code-testing/general.mdc, tests must be deterministic.
- Use auto-waiting locators (
locator.click()), neverpage.click(selector)on a maybe-not-ready node. - Replace
await page.waitForTimeout(...)withwaitForResponse,waitForURL, orexpect(locator).toBeVisible(). - Wait for the element state before acting on animated UI:
await locator.waitFor({ state: 'visible' }). - Quarantine genuinely flaky tests with
test.fixme(true, 'flaky — issue #NNN')and link an issue rather than leaving them red. - Reproduce flakiness with
npx playwright test path.spec.ts --repeat-each=10.
Done when
- Preconditions confirmed Playwright is present (otherwise deferred to manual/Pest/Dusk).
- Specs use POM and role/test-id selectors.
- The suite runs against the Laravel app on a dedicated testing env/database.
- Auth/network state is reused where it speeds the suite.
- No arbitrary sleeps; all waits are condition-based.
- CI runs the suite and uploads the report.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: pekral
- Source: pekral/cursor-rules
- License: MIT
- Homepage: https://pekral.cz
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.