一键导入
playwright-testing
Generer og kjør Playwright E2E-tester for webapplikasjoner med page objects, auth fixtures og tilgjengelighetstester
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generer og kjør Playwright E2E-tester for webapplikasjoner med page objects, auth fixtures og tilgjengelighetstester
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | playwright-testing |
| description | Generer og kjør Playwright E2E-tester for webapplikasjoner med page objects, auth fixtures og tilgjengelighetstester |
| license | MIT |
| compatibility | Node.js with Playwright |
| metadata | {"domain":"testing","tags":"playwright e2e testing accessibility responsive"} |
Generate Playwright tests for Nav web applications. Covers page object pattern, authentication fixtures, accessibility testing, and CI configuration.
playwright.config.ts# Install Playwright
pnpm add -D @playwright/test
pnpm exec playwright install --with-deps chromium
# Create configuration
pnpm exec playwright init
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile', use: { ...devices['Pixel 7'] } },
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})
// e2e/pages/oversikt.page.ts
import { type Locator, type Page, expect } from '@playwright/test'
export class OversiktPage {
readonly heading: Locator
readonly searchField: Locator
readonly table: Locator
constructor(private readonly page: Page) {
this.heading = page.getByRole('heading', { name: /oversikt/i })
this.searchField = page.getByRole('searchbox', { name: /søk/i })
this.table = page.getByRole('table')
}
async goto() {
await this.page.goto('/oversikt')
await expect(this.heading).toBeVisible()
}
async search(query: string) {
await this.searchField.fill(query)
await this.searchField.press('Enter')
}
async expectRowCount(count: number) {
const rows = this.table.getByRole('row')
// Minus header row
await expect(rows).toHaveCount(count + 1)
}
}
// e2e/fixtures/auth.ts
import { test as base } from '@playwright/test'
type AuthFixtures = {
authenticatedPage: ReturnType<(typeof base)['page']>
}
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page, context }, use) => {
// Set auth cookie for test environment
await context.addCookies([
{
name: 'selvbetjening-idtoken',
value: process.env.TEST_TOKEN ?? 'test-token',
domain: 'localhost',
path: '/',
},
])
await use(page)
},
})
export { expect } from '@playwright/test'
// e2e/tests/navigation.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Navigation', () => {
test('should navigate to oversikt page', async ({ page }) => {
await page.goto('/')
await page.getByRole('link', { name: /oversikt/i }).click()
await expect(page).toHaveURL(/\/oversikt/)
await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
})
test('should show 404 for unknown routes', async ({ page }) => {
const response = await page.goto('/ukjent-side')
expect(response?.status()).toBe(404)
})
})
// e2e/tests/form.spec.ts
import { test, expect } from '@playwright/test'
test.describe('Søknadsskjema', () => {
test('should submit form successfully', async ({ page }) => {
await page.goto('/soknad')
await page.getByLabel('Navn').fill('Ola Nordmann')
await page.getByLabel('E-post').fill('ola@nav.no')
await page.getByRole('combobox', { name: /tema/i }).selectOption('dagpenger')
await page.getByRole('button', { name: /send inn/i }).click()
await expect(page.getByRole('alert')).toContainText('Sendt')
})
test('should show validation errors', async ({ page }) => {
await page.goto('/soknad')
await page.getByRole('button', { name: /send inn/i }).click()
await expect(page.getByText('Navn er påkrevd')).toBeVisible()
await expect(page.getByText('E-post er påkrevd')).toBeVisible()
})
})
// e2e/tests/responsive.spec.ts
import { test, expect, devices } from '@playwright/test'
test.describe('Responsive', () => {
test('should show mobile menu on small screens', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 })
await page.goto('/')
await expect(page.getByRole('button', { name: /meny/i })).toBeVisible()
await expect(page.getByRole('navigation')).not.toBeVisible()
await page.getByRole('button', { name: /meny/i }).click()
await expect(page.getByRole('navigation')).toBeVisible()
})
test('should show full navigation on desktop', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 })
await page.goto('/')
await expect(page.getByRole('navigation')).toBeVisible()
})
})
// e2e/tests/accessibility.spec.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
test.describe('Accessibility', () => {
const pages = ['/', '/oversikt', '/usage']
for (const path of pages) {
test(`${path} should have no a11y violations`, async ({ page }) => {
await page.goto(path)
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21aa']).analyze()
expect(results.violations).toEqual([])
})
}
})
# .github/workflows/e2e.yml
e2e:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
Prioritized order for finding elements:
// 1. ✅ Role-based (best)
page.getByRole('button', { name: /send inn/i })
page.getByRole('heading', { level: 1 })
page.getByRole('link', { name: /oversikt/i })
// 2. ✅ Label-based (for form elements)
page.getByLabel('Fødselsnummer')
page.getByPlaceholder('Søk...')
// 3. ✅ Text-based (for static content)
page.getByText('Ingen resultater')
// 4. ⚠️ Test ID (only when role/label doesn't work)
page.getByTestId('metrics-chart')
// 5. ❌ CSS selectors (avoid)
page.locator('.my-class')
page.locator('#my-id')
page.waitForTimeout()test.describe to group related testsfullyParallel: true workstest-results/ directoryGenerer conventional commit-meldinger med Nav-relevante scopes og breaking change-format
Expert builder for the Aksel design system (Nav / @navikt) React components, design tokens, layout primitives, theming (light/dark), icons, CSS, the Tailwind preset, version migrations, and Figma-to-code. Trigger on any frontend UI task that mentions Aksel, Nav/Navikt, "designsystemet", or @navikt/ds-* / @navikt/aksel-* packages — or that asks to add, create, build, or refactor a component (button, input, modal, table, alert, card, form) or layout, or to implement a design from Figma (a pasted figma.com/design/...?node-id link, "implement this design", "build this from Figma", design-to-code). Strong signals "using/with aksel", "@navikt/ds-react", "design system", a pasted figma.com link. If the work is frontend UI and there is any Aksel signal, invoke this skill unless the user explicitly opts out.
Nais-deployment, GCP-ressurser, pod-lifecycle og feilsøking på plattformen
Generer Architecture Decision Records (ADR) med flerperspektiv-review tilpasset Nav
Azure AD, TokenX, ID-porten, Maskinporten og JWT-validering for Nav-applikasjoner
Strukturert intervju som avdekker blindsoner i Nav-prosjekter — personvern, auth, avhengigheter og observerbarhet