Skip to content

Instantly share code, notes, and snippets.

@up1
Last active March 22, 2026 15:33
Show Gist options
  • Select an option

  • Save up1/72f271f95c416cbaeed02527ece6d006 to your computer and use it in GitHub Desktop.

Select an option

Save up1/72f271f95c416cbaeed02527ece6d006 to your computer and use it in GitHub Desktop.
Playwright Skill for API Testing
$npx skills add testdino-hq/playwright-skill
███████╗██╗ ██╗██╗██╗ ██╗ ███████╗
██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝
███████╗█████╔╝ ██║██║ ██║ ███████╗
╚════██║██╔═██╗ ██║██║ ██║ ╚════██║
███████║██║ ██╗██║███████╗███████╗███████║
╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝
┌ skills
◇ Source: https://github.com/testdino-hq/playwright-skill.git
◇ Repository cloned
◇ Found 1 skill
● Skill: playwright-skill
│ Battle-tested Playwright patterns for E2E, API, component, visual, accessibility, and security testing. Covers locators, fixtures, POM, network mocking, auth flows, debugging, CI/CD (GitHub Actions, GitLab, CircleCI, Azure, Jenkins), framework recipes (React, Next.js, Vue, Angular), and migration guides from Cypress/Selenium. TypeScript and JavaScript.
◇ 42 agents
◆ Which agents do you want to install to?
│ ── Universal (.agents/skills) ── always included ────────────
│ • Amp
│ • Cline
│ • Codex
│ • Cursor
│ • Gemini CLI
│ • GitHub Copilot
│ • Kimi Code CLI
│ • OpenCode
│ • Warp
│ ── Additional agents ─────────────────────────────
│ Search:
│ ↑↓ move, space select, enter confirm
│ ❯ ○ Antigravity (.agent/skills)
│ ○ Augment (.augment/skills)
│ ○ Claude Code (.claude/skills)
│ ○ OpenClaw (skills)
│ ○ CodeBuddy (.codebuddy/skills)
│ ○ Command Code (.commandcode/skills)
│ ○ Continue (.continue/skills)
│ ○ Cortex Code (.cortex/skills)
│ ↓ 23 more
│ Selected: Amp, Cline, Codex +6 more
# 1. ทำการสร้าง Playwrigt project ใน folder demo-api
/playwright-skill create api testing in folder demo-api
# 2. ทำการสร้าง test casae จาก spec ที่ต้องการ
/playwright-skill add api testing from specs #api-specs/login-flow.md
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test.describe('GET /auth/me', () => {
test('returns current user profile with valid token', async ({ request }) => {
// Step 1: Login to get token
const loginResponse = await request.post('/auth/login', {
data: {
username: 'emilys',
password: 'emilyspass',
},
});
expect(loginResponse.ok()).toBeTruthy();
const { accessToken } = await loginResponse.json();
// Step 2: Get profile with token
const profileResponse = await request.get('/auth/me', {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
expect(profileResponse.status()).toBe(200);
const profile = await profileResponse.json();
expect(profile).toMatchObject({
id: expect.any(Number),
username: 'emilys',
email: 'emily.johnson@x.dummyjson.com',
firstName: 'Emily',
lastName: 'Johnson',
gender: 'female',
role: expect.any(String),
});
expect(profile.address).toMatchObject({
city: expect.any(String),
country: expect.any(String),
});
expect(profile.company).toMatchObject({
name: expect.any(String),
department: expect.any(String),
title: expect.any(String),
});
});
});
});
import { test, expect } from '@playwright/test';
import { z } from 'zod';
const LoginResponseSchema = z.looseObject({
id: z.number().int().positive(),
username: z.string().min(1),
email: z.email(),
firstName: z.string().min(1),
lastName: z.string().min(1),
gender: z.string().min(1),
image: z.url(),
accessToken: z.string().min(1),
refreshToken: z.string().min(1),
});
const ProfileResponseSchema = z.looseObject({
id: z.number().int().positive(),
username: z.string().min(1),
email: z.email(),
firstName: z.string().min(1),
lastName: z.string().min(1),
gender: z.string().min(1),
role: z.string().min(1),
address: z.looseObject({
city: z.string().min(1),
country: z.string().min(1),
}),
company: z.looseObject({
name: z.string().min(1),
department: z.string().min(1),
title: z.string().min(1),
}),
});
test.describe('Login Flow', () => {
test.describe('POST /auth/login', () => {
test('logs in with valid username and password', async ({ request }) => {
const response = await request.post('/auth/login', {
data: {
username: 'emilys',
password: 'emilyspass',
expiresInMins: 30,
},
});
expect(response.status()).toBe(200);
const body = await response.json();
const result = LoginResponseSchema.safeParse(body);
if (!result.success) {
throw new Error(`Schema validation failed:\n${z.prettifyError(result.error)}`);
}
});
});
test.describe('GET /auth/me', () => {
test('returns current user profile with valid token', async ({ request }) => {
// Step 1: Login to get token
const loginResponse = await request.post('/auth/login', {
data: {
username: 'emilys',
password: 'emilyspass',
},
});
expect(loginResponse.ok()).toBeTruthy();
const { accessToken } = await loginResponse.json();
// Step 2: Get profile with token
const profileResponse = await request.get('/auth/me', {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
expect(profileResponse.status()).toBe(200);
const profile = await profileResponse.json();
const result = ProfileResponseSchema.safeParse(profile);
if (!result.success) {
throw new Error(`Schema validation failed:\n${z.prettifyError(result.error)}`);
}
});
});
});
# Login flow
1. Login with username and password
2. Get current profile from authenticated token
## 1. Login with username and password
Request:
```
fetch('https://dummyjson.com/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'emilys',
password: 'emilyspass',
expiresInMins: 30, // optional, defaults to 60
}),
credentials: 'include' // Include cookies (e.g., accessToken) in the request
})
.then(res => res.json())
.then(console.log);
```
Response:
```
{
"id": 1,
"username": "emilys",
"email": "emily.johnson@x.dummyjson.com",
"firstName": "Emily",
"lastName": "Johnson",
"gender": "female",
"image": "https://dummyjson.com/icon/emilys/128",
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // JWT accessToken (for backward compatibility) in response and cookies
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." // refreshToken in response and cookies
}
```
## 2. Get current profile from authenticated token
Request:
```
/* providing accessToken in bearer */
fetch('https://dummyjson.com/auth/me', {
method: 'GET',
headers: {
'Authorization': 'Bearer /* YOUR_ACCESS_TOKEN_HERE */', // Pass JWT via Authorization header
},
credentials: 'include' // Include cookies (e.g., accessToken) in the request
})
.then(res => res.json())
.then(console.log);
```
Response:
```
{
"id": 1,
"firstName": "Emily",
"lastName": "Johnson",
"maidenName": "Smith",
"age": 29,
"gender": "female",
"email": "emily.johnson@x.dummyjson.com",
"phone": "+81 965-431-3024",
"username": "emilys",
"password": "emilyspass",
"birthDate": "1996-5-30",
"image": "https://dummyjson.com/icon/emilys/128",
"bloodGroup": "O-",
"height": 193.24,
"weight": 63.16,
"eyeColor": "Green",
"hair": {
"color": "Brown",
"type": "Curly"
},
"ip": "42.48.100.32",
"address": {
"address": "626 Main Street",
"city": "Phoenix",
"state": "Mississippi",
"stateCode": "MS",
"postalCode": "29112",
"coordinates": {
"lat": -77.16213,
"lng": -92.084824
},
"country": "United States"
},
"macAddress": "47:fa:41:18:ec:eb",
"university": "University of Wisconsin--Madison",
"bank": {
"cardExpire": "05/28",
"cardNumber": "3693233511855044",
"cardType": "Diners Club International",
"currency": "GBP",
"iban": "GB74MH2UZLR9TRPHYNU8F8"
},
"company": {
"department": "Engineering",
"name": "Dooley, Kozey and Cronin",
"title": "Sales Manager",
"address": {
"address": "263 Tenth Street",
"city": "San Francisco",
"state": "Wisconsin",
"stateCode": "WI",
"postalCode": "37657",
"coordinates": {
"lat": 71.814525,
"lng": -161.150263
},
"country": "United States"
}
},
"ein": "977-175",
"ssn": "900-590-289",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36",
"crypto": {
"coin": "Bitcoin",
"wallet": "0xb9fc2fe63b2a6c003f1c324c3bfa53259162181a",
"network": "Ethereum (ERC20)"
},
"role": "admin"
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment