원클릭으로
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 { expect, test } 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 { expect, test } 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 { devices, expect, test } 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 AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';
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.
Generer Architecture Decision Records (ADR) med flerperspektiv-review tilpasset Nav
Strukturert intervju som avdekker blindsoner i Nav-prosjekter — personvern, auth, avhengigheter og observerbarhet
Integrer og konfigurer Nav Dekoratøren – felles header og footer for nav.no-applikasjoner. Bruk når et team skal ta i bruk Dekoratøren, oppdatere konfigurasjon, legge til breadcrumbs/språkvelger/analytics, håndtere samtykke (ekomloven), CSP eller feilsøke integrasjon mot dekoratøren.
Arkitekturplanlegging med beslutningstrær for auth, kommunikasjon, database og Nais-konfigurasjon