- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
Playwright: Chụp Screenshot & Đính Kèm vào HTML Report
Playwright: Chụp Screenshot & Đính Kèm vào HTML Report
1. Tổng quan
Playwright cung cấp hai cách để lưu screenshot trong E2E test:
| Cách | Mô tả | Hiện trong report? |
|---|---|---|
page.screenshot({ path }) |
Lưu file ảnh ra disk | Không (phải tự mở file) |
testInfo.attach() |
Đính kèm vào HTML report | Có — hiện trong tab Attachments |
Bài này hướng dẫn kết hợp cả hai: lưu file ra disk và đính kèm vào report để dễ xem nhất.
2. Cài đặt
pnpm add -D @playwright/test
pnpm add dotenv
Cấu hình playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
reporter: 'html',
use: {
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
3. Chụp screenshot cơ bản
Full page
import { test } from '@playwright/test';
test('chụp full page', async ({ page }) => {
await page.goto('https://github.com');
await page.screenshot({
path: 'screenshots/github-home.png',
fullPage: true,
});
});
Chụp một element cụ thể
test('chụp element', async ({ page }) => {
await page.goto('https://github.com');
const header = page.locator('header');
await header.screenshot({ path: 'screenshots/github-header.png' });
});
Chụp theo vùng (clip)
test('chụp theo vùng', async ({ page }) => {
await page.goto('https://github.com');
await page.screenshot({
path: 'screenshots/github-clip.png',
clip: { x: 0, y: 0, width: 1280, height: 600 },
});
});
4. Đính kèm screenshot vào HTML Report
Dùng testInfo.attach() để Playwright nhúng ảnh trực tiếp vào HTML report. Screenshot sẽ hiện trong tab Attachments khi chạy npx playwright show-report.
import { test, type TestInfo } from '@playwright/test';
import * as path from 'path';
import * as fs from 'fs';
const SCREENSHOT_DIR = 'src/storage/screenshots';
function ensureDir(dir: string) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
test('screenshot với report attachment', async ({ page }, testInfo: TestInfo) => {
await page.goto('https://github.com');
ensureDir(SCREENSHOT_DIR);
const screenshotPath = path.join(SCREENSHOT_DIR, 'github-home.png');
// Bước 1: Lưu file ra disk
await page.screenshot({ path: screenshotPath, fullPage: true });
// Bước 2: Đính kèm vào HTML report
await testInfo.attach('github-home', {
path: screenshotPath,
contentType: 'image/png',
});
});
Lưu ý:
testInfo.attach()copy file vào thư mụcplaywright-report/data/nên ảnh sẽ không bị mất kể cả khi xóa thư mụcscreenshotsgốc.
5. Thực chiến: Login GitHub & Screenshot billing history
Cấu trúc thư mục
apps/core/
├── .env # credentials (không commit)
├── .env.example # template
└── tests/
└── ai-account/
└── github-login.spec.ts
Cấu hình .env
GITHUB_ACCOUNTS="account1@example.com,password1|account2@example.com,password2"
Format: email,password phân cách bởi | để hỗ trợ nhiều account.
.env.example
GITHUB_ACCOUNTS="account1@kozo-japan.com,xxx|account2@kozo-japan.com,xxx"
Full test file
import { test, type TestInfo } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import * as dotenv from 'dotenv';
// npx playwright test tests/ai-account/github-login.spec.ts --timeout 60000
// npx playwright test tests/ai-account/github-login.spec.ts --headed --timeout 60000
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
interface GithubAccount {
email: string;
password: string;
}
function parseGithubAccounts(): GithubAccount[] {
const raw = process.env.GITHUB_ACCOUNTS || '';
return raw
.split('|')
.map(entry => entry.trim())
.filter(Boolean)
.map(entry => {
const [email, password] = entry.split(',');
return { email: email.trim(), password: password.trim() };
});
}
const ACCOUNTS = parseGithubAccounts();
const SCREENSHOT_DIR = path.join(__dirname, '../../src/storage/ai-account/screenshots');
function ensureDir(dir: string) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
for (const account of ACCOUNTS) {
test(`Github login - ${account.email}`, async ({ page }, testInfo: TestInfo) => {
console.log(`\nLogging in as: ${account.email}`);
// Bước 1: Truy cập trang login
await page.goto('https://github.com/login');
await page.waitForSelector('#login_field', { timeout: 15000 });
// Bước 2: Điền credentials
await page.fill('#login_field', account.email);
await page.fill('#password', account.password);
await page.click('[name="commit"]');
// Bước 3: Chờ redirect ra khỏi /login (login thành công)
await page.waitForURL(url => !url.toString().includes('/login'), { timeout: 30000 });
console.log(`Login successful for: ${account.email}`);
// Bước 4: Navigate đến billing history
await page.goto('https://github.com/account/billing/history');
await page.waitForTimeout(2000);
console.log(`Navigated to billing history for: ${account.email}`);
// Bước 5: Lưu screenshot ra disk
ensureDir(SCREENSHOT_DIR);
const safeEmail = account.email.replace(/[^a-zA-Z0-9]/g, '_');
const screenshotPath = path.join(SCREENSHOT_DIR, `billing-history_${safeEmail}.png`);
await page.screenshot({ path: screenshotPath, fullPage: true });
console.log(`Screenshot saved: ${screenshotPath}`);
// Bước 6: Đính kèm vào Playwright HTML report
await testInfo.attach(`billing-history_${safeEmail}`, {
path: screenshotPath,
contentType: 'image/png',
});
});
}
6. Chạy test & xem report
# Chạy headless (mặc định)
npx playwright test tests/ai-account/github-login.spec.ts --timeout 60000
# Chạy có browser hiện lên (dễ debug)
npx playwright test tests/ai-account/github-login.spec.ts --headed --timeout 60000
# Chạy 1 account cụ thể bằng grep
npx playwright test tests/ai-account/github-login.spec.ts -g "account1@example.com" --timeout 60000
# Mở HTML report
npx playwright show-report
Sau khi chạy xong, mở report và click vào test case → tab Attachments sẽ hiện screenshot:
Tests
└── Github login - account1@example.com ✓ passed
└── Attachments
└── billing-history_account1_example_com.png [xem ảnh]
7. Các tùy chọn screenshot nâng cao
Tắt animations khi chụp
await page.screenshot({
path: 'screenshot.png',
fullPage: true,
animations: 'disabled', // tắt CSS animations để ảnh không bị blur
});
Chụp với scale cao hơn (retina)
await page.screenshot({
path: 'screenshot@2x.png',
scale: 'css', // hoặc 'device' cho retina
});
Attach buffer trực tiếp (không cần lưu file)
test('attach buffer', async ({ page }, testInfo: TestInfo) => {
await page.goto('https://github.com');
const buffer = await page.screenshot({ fullPage: true });
await testInfo.attach('github-home', {
body: buffer,
contentType: 'image/png',
});
});
Chụp nhiều bước trong cùng 1 test
test('multi-step screenshot', async ({ page }, testInfo: TestInfo) => {
await page.goto('https://github.com/login');
await testInfo.attach('step-1-login-page', {
body: await page.screenshot(),
contentType: 'image/png',
});
await page.fill('#login_field', 'user@example.com');
await page.fill('#password', 'password');
await page.click('[name="commit"]');
await page.waitForURL(url => !url.toString().includes('/login'));
await testInfo.attach('step-2-after-login', {
body: await page.screenshot(),
contentType: 'image/png',
});
});
Tóm tắt
| Mục tiêu | Giải pháp |
|---|---|
| Lưu ảnh ra disk | page.screenshot({ path: '...' }) |
| Hiện ảnh trong HTML report | testInfo.attach(name, { path, contentType }) |
| Attach không cần file | testInfo.attach(name, { body: buffer, contentType }) |
| Chụp full page | { fullPage: true } |
| Chụp element | locator.screenshot() |
| Tắt animations | { animations: 'disabled' } |
| Xem report | npx playwright show-report |
