ป้ายกำกับ: cross-browser

Playwright: ทดสอบเว็บบน Browser (Chromium, Firefox, Safari)Playwright: ทดสอบเว็บบน Browser (Chromium, Firefox, Safari)

นี่เป็นหนึ่งในจุดเด่นของ Playwright เลยที่สามารถรันการทดสอบข้าม Browser (Chromium, Firefox, Safari) (จริง ๆ แล้วตัวอื่น ๆ จะเป็นตัวที่เอา 3 ตัวนี้ไปดัดแปลง ฉนั้นเราเทสทั้ง 3 ตัวนี้ ก็ถือว่าเทสตัวอื่น ๆ เกือบหมดแล้ว ถ้ามีแปลก ๆ กว่านี้ ก็มีส่วนแบ่งไม่ถึง 1% คนใช้ต้องรับสภาพเอง) ได้พร้อมกันในการรันครั้งเดียว และรายงานผล (Report) จะถูกรวบรวมมารวมอยู่ในหน้าเดียวกัน โดยแยกให้เห็นชัดเจนว่าผลลัพธ์นั้นมาจาก Browser ตัวไหน

วิธีที่ดีที่สุดคือการตั้งค่าผ่านไฟล์ playwright.config.ts (หรือ .js) ครับ


🛠️ วิธีการตั้งค่าใน playwright.config.ts

ให้คุณไปที่ไฟล์คอนฟิกหลัก แล้วกำหนดค่าในส่วนของ projects โดยแยกตาม Browser ที่ต้องการ ดังนี้ครับ

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
	testDir: './tests',
	fullyParallel: true,
	reporter: [
		['list'],
		['monocart-reporter', {
			name: 'ระบบรายงานผลการทดสอบแยกตาม Browser และ Scope',
			outputFile: './test-results/report.html',
			columns: (defaultColumns) => {
				defaultColumns.push({
					id: 'testScope',
					name: 'Scope',
					type: 'string',
					width: 150,
					style: { fontWeight: 'bold', textAlign: 'center' },
					getValue: (row) => {
						const filePath = row.location?.file || '';
						// 🔥 ปรับ Logic: ถ้ามีชื่อโฟลเดอร์เฉพาะเบราว์เซอร์ให้พ่นตรงตัว ที่เหลือตีเป็น Cross-Browser ทั้งหมดอัตโนมัติ
						if (filePath.includes('browser-google-chrome-chromium')) return '🌐 Chrome Only';
						if (filePath.includes('browser-mozilla-firefox-gecko')) return '🦊 Firefox Only';
						if (filePath.includes('browser-apple-safari-webkit')) return '🍎 Safari Only';
						return '🌐 Cross-Browser';
					}
				});
			}
		}]
	],
	use: {
		screenshot: 'only-on-failure',
		video: 'retain-on-failure',
		trace: 'on-first-retry',
	},
	projects: [
		{
			name: 'Google Chrome',
			use: { ...devices['Desktop Chrome'] },
			testMatch: '**/*.spec.ts',
			// 🔥 สั่งข้ามโฟลเดอร์เฉพาะของ Firefox และ Safari
			testIgnore: ['**/browser-mozilla-firefox-gecko/**', '**/browser-apple-safari-webkit*/**'],
		},
		{
			name: 'Mozilla Firefox',
			use: { ...devices['Desktop Firefox'] },
			testMatch: '**/*.spec.ts',
			// 🔥 สั่งข้ามโฟลเดอร์เฉพาะของ Chrome และ Safari
			testIgnore: ['**/browser-google-chrome-chromium/**', '**/browser-apple-safari-webkit*/**'],
		},
		{
			name: 'Apple Safari',
			use: { ...devices['Desktop Safari'] },
			testMatch: '**/*.spec.ts',
			// 🔥 สั่งข้ามโฟลเดอร์เฉพาะของ Chrome และ Firefox
			testIgnore: ['**/browser-google-chrome-chromium/**', '**/browser-mozilla-firefox-gecko/**'],
		}
	],
});

เขียนสคริปต์ทดสอบบนทุก Browser

plusmagi-playwright/tests/browser-cross/cross-test.spec.ts

import { test, expect } from '@playwright/test';
import * as path from 'path';
import * as fs from 'fs';
import * as http from 'http'; // 🔥 เพิ่มโมดูล HTTP สำหรับสร้างเซิร์ฟเวอร์จริงในเครื่อง

test.describe('Global Cross-Browser Features', () => {
	let server: http.Server;
	let localServerUrl: string;

	// โหลดเซิร์ฟเวอร์จำลองขึ้นมาก่อนเริ่มรันเทสในไฟล์นี้
	test.beforeAll(async () => {
		server = http.createServer((req, res) => {
			if (req.url === '/') {
				res.writeHead(200, { 'Content-Type': 'text/html' });
				res.end('<html><body><a href="/download/some-file.txt" download>some-file.txt</a></body></html>');
			} else if (req.url === '/download/some-file.txt') {
				res.writeHead(200, {
					'Content-Type': 'application/octet-stream',
					'Content-Disposition': 'attachment; filename="some-file.txt"'
				});
				res.end('mock text file content');
			} else {
				res.writeHead(404);
				res.end();
			}
		});

		// ให้ระบบสุ่มพอร์ตว่างที่ปลอดภัยจากระบบปฏิบัติการ (พอร์ต 0 หมายถึงสุ่มพอร์ต)
		await new Promise<void>((resolve) => {
			server.listen(0, () => {
				const address = server.address();
				if (address && typeof address !== 'string') {
					localServerUrl = `http://localhost:${address.port}`;
				}
				resolve();
			});
		});
	});

	// ปิดเซิร์ฟเวอร์จำลองทันทีเมื่อรันเทสทุกเคสในไฟล์นี้เสร็จสิ้น ป้องกันพอร์ตค้าง
	test.afterAll(async () => {
		await new Promise<void>((resolve) => server.close(() => resolve()));
	});

	// 1. เคสทดสอบการคลิกปุ่ม Custom
	test('ควรรับส่ง Click Event บน Custom Component ได้ถูกต้อง', async ({ page }) => {
		await page.setContent(`
			<div id="custom-btn" role="button" tabindex="0" style="cursor:pointer; padding:10px; background:blue; color:white;">Click Me</div>
			<div id="click-status">Waiting</div>
			<script>
				document.getElementById('custom-btn').addEventListener('click', () => {
					document.getElementById('click-status').innerText = 'Clicked';
				});
			</script>
		`);
		await page.click('#custom-btn');
		await expect(page.locator('#click-status')).toHaveText('Clicked');
	});

	// 2. เคสทดสอบระบบ Offline
	test('แอปพลิเคชันต้องแสดงหน้าต่างเตือนเมื่อระบบอินเทอร์เน็ต Offline', async ({ context, page }) => {
		await page.setContent(`
			<button id="fetch-data">Load Data</button>
			<div id="network-error" style="display:none;">Internet Connection Error</div>
			<script>
				document.getElementById('fetch-data').addEventListener('click', async () => {
					try { await fetch('/api/data'); } catch { document.getElementById('network-error').style.display = 'block'; }
				});
			</script>
		`);

		try {
			await context.setOffline(true);
			await page.click('#fetch-data');
			await expect(page.locator('#network-error')).toBeVisible();
		} finally {
			await context.setOffline(false);
		}
	});

	// 3. เคสทดสอบฟอร์มทั่วไป
	test('ระบบฟอร์มต้องกรอกข้อมูลและกด Submit ได้อย่างสมบูรณ์', async ({ page }) => {
		await page.setContent(`
			<form id="test-form" onsubmit="event.preventDefault(); document.getElementById('output').innerText = 'Done';">
				<input type="text" id="username" required />
				<button type="submit" id="submit-btn">Submit</button>
			</form>
			<div id="output"></div>
		`);
		await page.fill('#username', 'pitt_dev');
		await page.click('#submit-btn');
		await expect(page.locator('#output')).toHaveText('Done');
	});

	// 4. เคสทดสอบดาวน์โหลดไฟล์ (เวอร์ชันสมบูรณ์ รองรับการรันแบบ Cross-Browser เคร่งครัด)
	test('ทดสอบการดาวน์โหลดไฟล์ และตรวจสอบว่าไฟล์ถูกเขียนลงดิสก์จริง', async ({ page }) => {
		// วิ่งไปยังเซิร์ฟเวอร์ภายในเครื่องที่เราจำลองขึ้นมาจริง ๆ
		await page.goto(localServerUrl);

		const downloadLink = page.getByRole('link', { name: 'some-file.txt', exact: true });

		// ดักฟัง Event ดาวน์โหลด (รอบนี้จะทำงานฉลุยทุกเบราว์เซอร์เพราะเป็น Socket เน็ตเวิร์กจริง)
		const [download] = await Promise.all([
			page.waitForEvent('download'),
			downloadLink.click()
		]);

		expect(download.suggestedFilename()).toBe('some-file.txt');

		const savePath = path.join(__dirname, '../downloads', download.suggestedFilename());
		await download.saveAs(savePath);

		expect(fs.existsSync(savePath)).toBe(true);

		try {
			fs.unlinkSync(savePath);
		} catch (err) {
			console.error('ไม่สามารถลบไฟล์ทดสอบได้:', err);
		}
	});

});

ในอนาคตอาจจะเพิ่มโฟลเดอร์ฟีเจอร์ใหม่อีกหลายโฟลเดอร์ที่ระดับราก เช่น tests/unit-authentication, tests/sales-commission) ตัว Playwright จะดักจับไปรันเป็น Cross-Browser ครบทั้ง 3 เบราว์เซอร์ให้ทันที โดยที่ไม่ต้องกลับมาเปิดไฟล์ Config นี้เพื่อพิมพ์เพิ่มพาทอีกเลยครับตลอดทั้งโปรเจกต์


🚀 วิธีการรันและการดู Report


สั่งรันคำสั่งปกติ

เมื่อคุณรันคำสั่งนี้ Playwright จะวิ่งทดสอบทุกไฟล์กับทุก Browser ที่เราตั้งค่าไว้ใน projects พร้อมกันโดยอัตโนมัติ
npx playwright test


รันทุกเบราว์เซอร์เฉพาะโฟลเดอร์นั้น (แนะนำสำหรับการตรวจ Cross-Browser)

คำสั่งนี้จะสั่งให้ Playwright วิ่งเข้าไปรันเฉพาะสคริปต์ที่อยู่ในโฟลเดอร์ unit-authentication แต่จะกระจายรันครบทุกเบราว์เซอร์ (Chrome, Firefox, Safari) ตามที่เราตั้งค่าไว้ใน Config ครับ
npx playwright test tests/unit-authentication


เจาะจงรันเฉพาะโฟลเดอร์ + เฉพาะเบราว์เซอร์ที่เลือก (เร็วที่สุด เหมาะกับตอน Dev)

หากคุณกำลังโค้ดฟีเจอร์นี้อยู่ แล้วอยากเทสเร็ว ๆ บน Chrome ตัวเดียว ไม่ต้องรันเบราว์เซอร์อื่นให้เสียเวลา ให้เติมแฟลก --project เข้าไปครับ
npx playwright test tests/unit-authentication --project="Google Chrome"


เปิดรันผ่าน UI Mode เจาะจงโฟลเดอร์

ถ้าต้องการเปิดหน้าต่าง UI ของ Playwright ขึ้นมาดูสเต็ปการรัน, ดูหน้าจอสกรีนช็อตแบบ Real-time และเจาะจงดูเฉพาะโฟลเดอร์นี้ ให้ใช้คำสั่ง
npx playwright test tests/unit-authentication --ui


ถ้าต้องการรันเฉพาะไฟล์เจาะจง

สามารถพิมพ์ลึกไปถึงชื่อไฟล์ได้เลย เช่น
npx playwright test tests/unit-authentication/login.spec.ts


ผลลัพธ์ใน HTML Report

หลังจากรันเสร็จ หน้าต่าง Report (HTML) จะเปิดขึ้นมา โดยโครงสร้างหน้าตาจะเป็นแบบนี้

  • จะมีแถบ Filter (ตัวกรอง) ด้านบนให้คุณเลือกกดดูเฉพาะ chromium, firefox หรือ webkit ได้
  • ในรายชื่อ Test Case แต่ละข้อ จะมีป้ายกำกับ (Tag) เล็กๆ บอกชัดเจน เช่น example.spec.ts > chromium, example.spec.ts > webkit
  • หากมี Case ไหนพัง (Failed) คุณสามารถกดเข้าไปดูรายละเอียด Screenshot หรือ Video แยกตาม Browser ตัวที่พังได้เลย

💡 ข้อควรระวังเรื่องทรัพยากรเครื่อง

การรัน 3 Browser พร้อมกันค่อนข้างกิน RAM และ CPU หากเครื่องคอมพิวเตอร์ของคุณเริ่มหน่วงหรือค้าง สามารถจำกัดจำนวนการรันพร้อมกันได้โดยเพิ่ม workers: 1 หรือ workers: 2 ในไฟล์ config เพื่อให้มันทยอยรันทีละตัว แต่สุดท้าย Report ก็ยังจะมารวมอยู่ที่หน้าเดียวกันเหมือนเดิมครับ


อ่านเพิ่มเติม