Tự động hóa Visual Regression Testing đa khu vực bằng Residential Proxy
21 tháng 9, 2026
Tại sao cần Visual Regression Testing đa khu vực
Khi triển khai ứng dụng web toàn cầu, giao diện có thể hiển thị khác nhau do font, RTL, kích thước màn hình, CDN hoặc chính sách nội dung theo vùng. Kiểm thử thủ công trên từng quốc gia tốn thời gian và dễ bỏ sót. Visual Regression Testing (VRT) tự động so sánh screenshot giữa bản baseline và bản mới, phát hiện sự thay đổi không mong muốn ở cấp độ pixel.
Sử dụng residential proxy cho VRT mang lại hai lợi thế chính:
- Địa chỉ IP thật của nhà cung cấp dịch vụ Internet → trang web không chặn hoặc hiển thị nội dung giả lập.
- Geo‑targeting chính xác → proxy cho phép chọn quốc gia, thậm chí thành phố, để mô phỏng người dùng địa phương.
Chọn Residential Proxy thay vì Datacenter
| Tiêu chí | Residential Proxy | Datacenter Proxy |
|---|---|---|
| Độ tin cậy IP | Cao (IP của ISP thật) | Thấp (dễ bị blacklist) |
| Geo‑targeting | Cấp độ quốc gia/thành phố | Thường chỉ cấp độ quốc gia |
| Chi phí | Cao hơn | Rẻ hơn |
| Phù hợp VRT | Rất phù hợp | Không khuyến nghị |
Vì VRT nhạy cảm với nội dung động (quảng cáo, banner theo vùng), residential proxy giảm tỷ lệ false‑positive do chặn IP hoặc nội dung thay thế.
Kiến trúc pipeline
- Playwright điều khiển trình duyệt headless/chromium.
- Proxy pool (RoProxy) cung cấp danh sách residential IP kèm quốc gia.
- Baseline store (Git LFS hoặc S3) lưu screenshot chuẩn cho từng region.
- Pixelmatch (hoặc
playwright/testbuilt‑in) so sánh pixel và tạo diff. - CI/CD (GitHub Actions) chạy pipeline trên mỗi PR và lên lịch hàng ngày.
1. Cấu hình Playwright với Proxy
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: 1,
reporter: [['html', { outputFolder: 'playwright-report' }]],
use: {
baseURL: 'https://example.com',
trace: 'on-first-retry',
// Proxy sẽ được inject ở runtime qua test.use()
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
],
});
Trong file test, chúng ta inject proxy theo region:
// tests/visual-regression.spec.ts
import { test, expect } from '@playwright/test';
import fs from 'fs';
import path from 'path';
import pixelmatch from 'pixelmatch';
import { PNG } from 'pngjs';
const regions = [
{ country: 'US', city: 'New York' },
{ country: 'VN', city: 'Ho Chi Minh City' },
{ country: 'DE', city: 'Berlin' },
];
for (const region of regions) {
test(`Visual regression - ${region.country}-${region.city}`, async ({ page }) => {
// Lấy proxy từ RoProxy API (giả lập)
const proxy = await fetchProxy(region.country, region.city);
await page.context().setProxy({ server: proxy.server, username: proxy.user, password: proxy.pass });
await page.goto('/');
await page.waitForLoadState('networkidle');
const screenshotPath = `screenshots/${region.country}-${region.city}.png`;
await page.screenshot({ path: screenshotPath, fullPage: true });
// So sánh với baseline
const baselinePath = `baseline/${region.country}-${region.city}.png`;
if (fs.existsSync(baselinePath)) {
const diff = await compareImages(baselinePath, screenshotPath);
expect(diff.mismatchPercent).toBeLessThan(0.1); // ngưỡng 0.1%
} else {
// Lần đầu: lưu baseline
fs.copyFileSync(screenshotPath, baselinePath);
console.log(`Baseline created for ${region.country}-${region.city}`);
}
});
}
async function fetchProxy(country: string, city: string) {
// Gọi RoProxy API để lấy residential proxy theo geo
const resp = await fetch(`https://api.roproxy.com/v1/proxy?country=${country}&city=${encodeURIComponent(city)}`, {
headers: { Authorization: `Bearer ${process.env.ROPROXY_TOKEN}` },
});
const data = await resp.json();
return data.proxy; // { server, user, pass }
}
async function compareImages(baseline: string, current: string) {
const img1 = PNG.sync.read(fs.readFileSync(baseline));
const img2 = PNG.sync.read(fs.readFileSync(current));
const { width, height } = img1;
const diff = new PNG({ width, height });
const mismatch = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
fs.writeFileSync(`diff/${path.basename(baseline)}`, PNG.sync.write(diff));
return { mismatchPercent: (mismatch / (width * height)) * 100 };
}
Giải thích:
fetchProxygọi API RoProxy để lấy residential IP phù hợp với region.page.context().setProxyáp dụng proxy cho toàn bộ context, đảm bảo mọi request (HTML, CSS, JS, font) đi qua IP đó.pixelmatchtính toán phần trăm pixel khác biệt; ngưỡng 0.1% phù hợp cho thay đổi nhỏ do rendering.
2. Thu thập screenshot baseline
Chạy lần đầu trên môi trường staging với lệnh:
npm run test:visual -- --update-baseline
Trong package.json thêm script:
"test:visual": "playwright test tests/visual-regression.spec.ts",
"test:visual:update": "playwright test tests/visual-regression.spec.ts --update-baseline"
Khi chạy với --update-baseline, test sẽ copy screenshot hiện tại vào thư mục baseline/. Baseline nên được commit vào repo (hoặc lưu trên S3 với versioning) để các lần chạy sau so sánh.
3. Chạy so sánh trên nhiều region song song
Playwright hỗ trợ fullyParallel: true. Với 3 region, mỗi region chạy trên một worker riêng, tổng thời gian gần bằng thời gian region chậm nhất. Đảm bảo quota proxy đủ lớn (ít nhất 1 IP/region đồng thời).
4. Tích hợp CI/CD (GitHub Actions)
# .github/workflows/visual-regression.yml
name: Visual Regression
on:
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * *' # hàng ngày lúc 02:00 UTC
jobs:
visual-regression:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
region: [US-NY, VN-HCM, DE-BER]
steps:
- uses: actions/checkout@v4
with:
lfs: true # cần cho baseline ảnh lớn
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install deps
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run visual test for ${{ matrix.region }}
env:
ROPROXY_TOKEN: ${{ secrets.ROPROXY_TOKEN }}
run: |
REGION=${{ matrix.region }}
COUNTRY=${REGION%%-*}
CITY=${REGION#*-}
npx playwright test tests/visual-regression.spec.ts --grep "${COUNTRY}-${CITY}"
- name: Upload diff artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: diff-${{ matrix.region }}
path: diff/*.png
retention-days: 7
Điểm quan trọng:
matrix.regionchạy song song 3 region.secrets.ROPROXY_TOKENlưu token API RoProxy an toàn.lfs: trueđảm bảo baseline ảnh được pull về runner.- Artifact diff giúp review nhanh khi test fail.
Xử lý flakiness và tối ưu
- Chờ networkidle – đảm bảo tài nguyên (font, ảnh) tải xong trước khi chụp.
- Tắt animation – inject CSS
*, *::before, *::after { animation: none !important; transition: none !important; }quapage.addStyleTag. - Mask vùng động – dùng
page.locator('.ads-banner').evaluate(el => el.style.visibility='hidden')hoặc Playwrightmaskoption trongpage.screenshot({ mask: [page.locator('.dynamic-content')] }). - Retry thông minh – cấu hình
retries: 2và chỉ retry khi mismatch > ngưỡng do network chậm. - Proxy health check – trước khi chạy test, ping proxy endpoint
/healthcủa RoProxy; loại IP lỗi khỏi pool.
Kết luận
Kết hợp Residential Proxy với Playwright Visual Regression cho phép đội ngũ phát hiện sớm sự cố giao diện đặc thù từng thị trường mà không cần duy trì hạ tầng vật lý tại nhiều quốc gia. Quy trình trên có thể mở rộng thêm region, tích hợp vào pipeline release, và kết hợp với các công cụ monitoring (Grafana, Sentry) để có bức tranh toàn diện về trải nghiệm người dùng toàn cầu.
Hãy bắt đầu bằng một region, thiết lập baseline, sau đó mở rộng dần. Với RoProxy cung cấp pool residential ổn định, bạn sẽ giảm thiểu false‑positive do chặn IP và tập trung vào chất lượng sản phẩm.