Quay lại danh sách
Tự động hóa Visual Regression Testing đa khu vực bằng Residential Proxy

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

  1. Playwright điều khiển trình duyệt headless/chromium.
  2. Proxy pool (RoProxy) cung cấp danh sách residential IP kèm quốc gia.
  3. Baseline store (Git LFS hoặc S3) lưu screenshot chuẩn cho từng region.
  4. Pixelmatch (hoặc playwright/test built‑in) so sánh pixel và tạo diff.
  5. 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:

  • fetchProxy gọ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 đó.
  • pixelmatch tí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.region chạy song song 3 region.
  • secrets.ROPROXY_TOKEN lư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

  1. Chờ networkidle – đảm bảo tài nguyên (font, ảnh) tải xong trước khi chụp.
  2. Tắt animation – inject CSS *, *::before, *::after { animation: none !important; transition: none !important; } qua page.addStyleTag.
  3. Mask vùng động – dùng page.locator('.ads-banner').evaluate(el => el.style.visibility='hidden') hoặc Playwright mask option trong page.screenshot({ mask: [page.locator('.dynamic-content')] }).
  4. Retry thông minh – cấu hình retries: 2 và chỉ retry khi mismatch > ngưỡng do network chậm.
  5. Proxy health check – trước khi chạy test, ping proxy endpoint /health củ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.