[Playwright] Tất tần tật về input text

Trong quá trình automation testing, việc xử lý các trường nhập liệu văn bản (input) là một trong những thao tác cơ bản nhưng cực kỳ quan trọng. Playwright cung cấp nhiều phương thức mạnh mẽ để làm việc với text input, từ những thao tác đơn giản đến xử lý các tình huống phức tạp. Bài viết này sẽ hướng dẫn chi tiết cách xử lý input dạng text trong Playwright TypeScript.
Các phương thức cơ bản
1. Fill – Phương thức phổ biến nhất
Phương thức fill() là cách nhanh nhất và đơn giản nhất để nhập văn bản vào một trường input. Nó sẽ xóa toàn bộ nội dung hiện tại và thay thế bằng văn bản mới.
import { test, expect } from '@playwright/test';
test('Nhập văn bản với fill()', async ({ page }) => {
await page.goto('https://example.com/form');
// Nhập văn bản vào trường username
await page.fill('#username', 'testuser@example.com');
// Hoặc sử dụng với locator
const emailInput = page.locator('input[type="email"]');
await emailInput.fill('admin@company.com');
// Verify giá trị đã nhập
await expect(emailInput).toHaveValue('admin@company.com');
});
2. Type – Mô phỏng thao tác gõ phím
Khác với fill(), phương thức type() mô phỏng việc gõ từng ký tự một, phù hợp khi cần test các sự kiện keyboard hoặc auto-complete.
test('Nhập văn bản với type()', async ({ page }) => {
await page.goto('https://example.com/search');
const searchBox = page.locator('#search-input');
// Gõ từng ký tự với độ trễ 100ms
await searchBox.type('Playwright testing', { delay: 100 });
// Type() không xóa nội dung cũ, cần clear trước nếu muốn
await searchBox.clear();
await searchBox.type('New search term');
});
3. PressSequentially – Phương thức mới trong Playwright
pressSequentially() là phương thức được giới thiệu gần đây, tương tự như type() nhưng với nhiều tùy chọn hơn.
test('Sử dụng pressSequentially()', async ({ page }) => {
await page.goto('https://example.com/form');
const input = page.locator('#dynamic-input');
// Nhập với độ trễ giữa các ký tự
await input.pressSequentially('Test automation', {
delay: 50 // milliseconds giữa mỗi ký tự
});
// Kết hợp với các phím đặc biệt
await input.pressSequentially('Hello');
await page.keyboard.press('Control+A'); // Select all
await input.pressSequentially('Replaced text');
});
Xử lý các tình huống đặc biệt
1. Clear và nhập lại văn bản
test('Clear và nhập văn bản mới', async ({ page }) => {
await page.goto('https://example.com/profile');
const nameInput = page.locator('#name');
// Cách 1: Dùng clear()
await nameInput.clear();
await nameInput.fill('John Doe');
// Cách 2: Dùng fill() trực tiếp (tự động clear)
await nameInput.fill('Jane Smith');
// Cách 3: Select all và ghi đè
await nameInput.click();
await page.keyboard.press('Control+A');
await nameInput.type('New Name');
});
2. Xử lý input có validation
test('Xử lý input với validation', async ({ page }) => {
await page.goto('https://example.com/register');
const passwordInput = page.locator('#password');
const errorMessage = page.locator('.error-message');
// Nhập password không hợp lệ
await passwordInput.fill('123');
await passwordInput.blur(); // Trigger validation
// Verify error message xuất hiện
await expect(errorMessage).toBeVisible();
await expect(errorMessage).toContainText('Password must be at least 8 characters');
// Nhập password hợp lệ
await passwordInput.fill('SecureP@ssw0rd');
await expect(errorMessage).not.toBeVisible();
});
3. Xử lý input với mask hoặc format
test('Xử lý input có mask (phone, date)', async ({ page }) => {
await page.goto('https://example.com/contact');
// Phone number với mask
const phoneInput = page.locator('#phone');
await phoneInput.fill('1234567890');
// Verify format được apply
const formattedValue = await phoneInput.inputValue();
expect(formattedValue).toBe('(123) 456-7890');
// Date input
const dateInput = page.locator('input[type="date"]');
await dateInput.fill('2024-12-25');
// Credit card với spaces
const cardInput = page.locator('#credit-card');
await cardInput.type('4111111111111111');
const cardValue = await cardInput.inputValue();
expect(cardValue).toBe('4111 1111 1111 1111');
});
Kỹ thuật nâng cao
1. Xử lý auto-complete và dropdown suggestions
test('Xử lý auto-complete', async ({ page }) => {
await page.goto('https://example.com/location');
const cityInput = page.locator('#city-search');
// Gõ từ khóa để trigger auto-complete
await cityInput.type('New');
// Đợi dropdown suggestions xuất hiện
await page.waitForSelector('.autocomplete-suggestions');
// Chọn option từ suggestions
await page.locator('.suggestion-item:has-text("New York")').click();
// Verify giá trị được chọn
await expect(cityInput).toHaveValue('New York, NY, USA');
});
2. Upload file qua input
test('Upload file', async ({ page }) => {
await page.goto('https://example.com/upload');
// Upload single file
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles('path/to/document.pdf');
// Upload multiple files
await fileInput.setInputFiles([
'path/to/file1.jpg',
'path/to/file2.png'
]);
// Clear selected files
await fileInput.setInputFiles([]);
});
3. Xử lý contenteditable elements
test('Xử lý contenteditable', async ({ page }) => {
await page.goto('https://example.com/editor');
const editor = page.locator('[contenteditable="true"]');
// Click vào editor
await editor.click();
// Clear content hiện tại
await page.keyboard.press('Control+A');
await page.keyboard.press('Delete');
// Nhập nội dung mới
await editor.type('This is rich text content');
// Format text
await page.keyboard.press('Control+A');
await page.keyboard.press('Control+B'); // Bold
// Verify content
const content = await editor.innerText();
expect(content).toBe('This is rich text content');
});
Best Practices và Tips
1. Waiting strategies cho input
test('Waiting strategies', async ({ page }) => {
await page.goto('https://example.com/dynamic-form');
// Đợi input enabled trước khi nhập
const input = page.locator('#delayed-input');
await input.waitFor({ state: 'visible' });
await expect(input).toBeEnabled();
await input.fill('Test data');
// Đợi sau khi nhập để đảm bảo value được update
await page.waitForTimeout(500); // Tránh dùng, chỉ khi cần thiết
// Better: Đợi một điều kiện cụ thể
await expect(input).toHaveValue('Test data');
});
2. Error handling
test('Error handling cho input', async ({ page }) => {
await page.goto('https://example.com/form');
try {
// Set timeout ngắn cho input cụ thể
await page.locator('#non-existent').fill('test', { timeout: 5000 });
} catch (error) {
console.log('Input không tồn tại hoặc không thể nhập');
// Handle error hoặc use alternative approach
}
// Retry mechanism
const retryFill = async (selector: string, text: string, retries = 3) => {
for (let i = 0; i < retries; i++) {
try {
await page.fill(selector, text);
return;
} catch (error) {
if (i === retries - 1) throw error;
await page.waitForTimeout(1000);
}
}
};
await retryFill('#flaky-input', 'Test text');
});
- Tìm hiểu thêm: dùng
expect.pollvàexpect.toPass
3. Validation helpers
test('Validation helpers', async ({ page }) => {
await page.goto('https://example.com/form');
// Helper function để validate input
const validateAndFill = async (
selector: string,
value: string,
expectedPattern?: RegExp
) => {
const input = page.locator(selector);
await input.fill(value);
if (expectedPattern) {
const actualValue = await input.inputValue();
expect(actualValue).toMatch(expectedPattern);
}
return input;
};
// Sử dụng helper
await validateAndFill('#email', 'test@example.com', /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/);
await validateAndFill('#phone', '1234567890', /^\d{10}$/);
});
