یونٹ ٹیسٹ اس بات کی تصدیق کرتے ہیں کہ انفرادی افعال صحیح طریقے سے برتاؤ کرتے ہیں۔ انٹیگریشن ٹیسٹ اس بات کی تصدیق کرتے ہیں کہ خدمات ایک ساتھ کام کرتی ہیں۔ لیکن نہ ہی یہ سمجھتا ہے کہ جب صارف آپ کی ایپلیکیشن پر کلک کرتا ہے تو کیا ٹوٹتا ہے — ٹوٹا ہوا نیویگیشن فلو، جاوا اسکرپٹ ہائیڈریشن کی خرابیاں، نیٹ ورک پر منحصر رینڈرنگ کی ناکامیاں، اور کراس براؤزر رینڈرنگ کیڑے۔ پلے رائٹ کے ساتھ اینڈ ٹو اینڈ ٹیسٹ اس خلا کو پُر کرتے ہیں، اور وہ اپنے سائپرس یا سیلینیم پیشرووں سے کہیں زیادہ قابل اعتماد ہیں۔
ڈرامہ نگار حقیقی Chromium، Firefox، اور WebKit براؤزرز کو متوازی طور پر چلاتا ہے۔ یہ بات چیت کرنے سے پہلے عناصر کے مرئی اور مستحکم ہونے کا خود بخود انتظار کرتا ہے۔ یہ ناکامی پر اسکرین شاٹس، ویڈیوز اور نیٹ ورک کے نشانات کو پکڑتا ہے۔ اور اس کا TypeScript-first API لکھنے کے قابل ٹیسٹوں کو خوشی دیتا ہے۔ اس گائیڈ میں Next.js ایپلی کیشنز کے لیے کنفیگریشن سے لے کر پیج آبجیکٹ تک CI انٹیگریشن تک سب کچھ شامل ہے۔
اہم ٹیک ویز
data-testidسلیکٹرز کو خصوصی طور پر استعمال کریں — CSS سلیکٹرز اور ٹیکسٹ سلیکٹرز UI تبدیلیوں پر ٹوٹ جاتے ہیں- صفحہ آبجیکٹ ماڈل (POM) سلیکٹرز کو دوبارہ قابل استعمال کلاسوں میں خلاصہ کرتا ہے - فی UI عنصر میں ایک تبدیلی کی جگہ
- ہمیشہ
await page.goto()+await expect(page).toHaveURL()استعمال کریں — کبھی بھی یہ مت سمجھیں کہ نیویگیشن ہم آہنگی سے مکمل ہوتی ہے- تیز رفتار CI رنز کے لیے
test.describe.parallel()اور--workersپرچم استعمال کریں۔playwright codegenکے ساتھ ٹیسٹ ریکارڈ کریں لیکن ارتکاب کرنے سے پہلے صفحہ کی اشیاء کو ہمیشہ ریفیکٹر کریں۔- ترتیب میں
screenshot: 'only-on-failure'کے ذریعے ناکامی پر اسکرین شاٹس کیپچر کریں۔@axe-core/playwrightکے ساتھ رویے کے ساتھ ساتھ رسائی کی جانچ کریں- فلیکی تھرڈ پارٹی کالز کے لیے موک API جوابات؛ خوش کن راستوں کے لیے حقیقی API کا استعمال کریں۔
کنفیگریشن
// apps/web/playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'playwright-report/results.json' }],
...(process.env.CI ? [['github'] as ['github']] : []),
],
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 7'] },
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 14'] },
},
],
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
});
ٹیسٹ ڈائرکٹری کا ڈھانچہ
tests/
public/ # Tests for unauthenticated public pages
homepage.spec.ts
blog.spec.ts
services.spec.ts
sitemap.spec.ts
i18n.spec.ts
seo.spec.ts
admin/ # Tests for authenticated admin pages
contacts.spec.ts
orders.spec.ts
dashboard.spec.ts
fixtures/
auth.fixture.ts # Authentication fixtures
data.fixture.ts # Test data factories
pages/ # Page Object Model classes
HomePage.ts
BlogPage.ts
ContactsPage.ts
LoginPage.ts
helpers/
api.helper.ts # API call helpers for test setup
صفحہ آبجیکٹ ماڈل
صفحہ آبجیکٹ ماڈل برقرار رکھنے کے قابل E2E ٹیسٹوں کے لیے واحد سب سے اہم نمونہ ہے۔ جب سلیکٹر تبدیل ہوتا ہے، تو آپ ایک جگہ کو اپ ڈیٹ کرتے ہیں — ہر اس ٹیسٹ کو نہیں جو اسے استعمال کرتا ہے۔
// tests/pages/LoginPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
readonly forgotPasswordLink: Locator;
constructor(page: Page) {
this.page = page;
// Always use data-testid selectors
this.emailInput = page.getByTestId('login-email');
this.passwordInput = page.getByTestId('login-password');
this.submitButton = page.getByTestId('login-submit');
this.errorMessage = page.getByTestId('login-error');
this.forgotPasswordLink = page.getByTestId('forgot-password-link');
}
async goto() {
await this.page.goto('/auth/login');
await expect(this.page).toHaveURL('/auth/login');
await expect(this.submitButton).toBeVisible();
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async loginAndWaitForRedirect(email: string, password: string, expectedUrl: string) {
await this.login(email, password);
await expect(this.page).toHaveURL(expectedUrl, { timeout: 10_000 });
}
async expectError(message: string) {
await expect(this.errorMessage).toBeVisible();
await expect(this.errorMessage).toContainText(message);
}
}
// tests/pages/ContactsPage.ts
import { Page, Locator, expect } from '@playwright/test';
export class ContactsPage {
readonly page: Page;
readonly addButton: Locator;
readonly searchInput: Locator;
readonly table: Locator;
readonly dialog: Locator;
readonly nameInput: Locator;
readonly emailInput: Locator;
readonly saveButton: Locator;
readonly successToast: Locator;
constructor(page: Page) {
this.page = page;
this.addButton = page.getByTestId('contacts-add-button');
this.searchInput = page.getByTestId('contacts-search');
this.table = page.getByTestId('contacts-table');
this.dialog = page.getByTestId('contact-dialog');
this.nameInput = page.getByTestId('contact-name-input');
this.emailInput = page.getByTestId('contact-email-input');
this.saveButton = page.getByTestId('contact-save-button');
this.successToast = page.getByTestId('toast-success');
}
async goto() {
await this.page.goto('/dashboard/contacts');
await expect(this.table).toBeVisible();
}
async openAddDialog() {
await this.addButton.click();
await expect(this.dialog).toBeVisible();
}
async createContact(name: string, email: string) {
await this.openAddDialog();
await this.nameInput.fill(name);
await this.emailInput.fill(email);
await this.saveButton.click();
await expect(this.successToast).toBeVisible();
}
async searchFor(query: string) {
await this.searchInput.fill(query);
await this.page.waitForTimeout(500); // Debounce delay
}
async getRowCount() {
return this.table.getByRole('row').count();
}
async expectContactInTable(name: string) {
await expect(this.table).toContainText(name);
}
}
تصدیقی فکسچر
// tests/fixtures/auth.fixture.ts
import { test as base, Page } from '@playwright/test';
type AuthFixture = {
adminPage: Page;
userPage: Page;
};
export const test = base.extend<AuthFixture>({
adminPage: async ({ browser }, use) => {
const context = await browser.newContext();
const page = await context.newPage();
// Login via API to bypass UI (faster, more reliable)
const response = await page.request.post('/auth/login', {
data: {
email: process.env.TEST_ADMIN_EMAIL || '[email protected]',
password: process.env.TEST_ADMIN_PASSWORD || 'testpassword123',
},
});
if (!response.ok()) {
throw new Error(`Auth failed: ${response.status()}`);
}
// Cookies are set automatically on the context
await use(page);
await context.close();
},
userPage: async ({ browser }, use) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.request.post('/auth/login', {
data: {
email: process.env.TEST_USER_EMAIL || '[email protected]',
password: process.env.TEST_USER_PASSWORD || 'testpassword123',
},
});
await use(page);
await context.close();
},
});
export { expect } from '@playwright/test';
تحریری ٹیسٹ
// tests/admin/contacts.spec.ts
import { test, expect } from '../fixtures/auth.fixture';
import { ContactsPage } from '../pages/ContactsPage';
test.describe('Contacts Management', () => {
test('admin can create, search, and delete a contact', async ({ adminPage }) => {
const contactsPage = new ContactsPage(adminPage);
const testName = `Test User ${Date.now()}`;
const testEmail = `test-${Date.now()}@example.com`;
await contactsPage.goto();
// Create
await contactsPage.createContact(testName, testEmail);
await contactsPage.expectContactInTable(testName);
// Search
await contactsPage.searchFor(testName);
const rowCount = await contactsPage.getRowCount();
expect(rowCount).toBe(2); // 1 header + 1 result
// Delete
await adminPage.getByTestId(`contact-delete-${testEmail}`).click();
await adminPage.getByTestId('confirm-delete').click();
await expect(contactsPage.successToast).toBeVisible();
await expect(contactsPage.table).not.toContainText(testName);
});
test('shows validation errors for invalid contact data', async ({ adminPage }) => {
const contactsPage = new ContactsPage(adminPage);
await contactsPage.goto();
await contactsPage.openAddDialog();
// Submit without filling required fields
await contactsPage.saveButton.click();
await expect(adminPage.getByTestId('error-name')).toContainText('required');
await expect(adminPage.getByTestId('error-email')).toContainText('required');
});
});
پبلک پیج ٹیسٹ
// tests/public/homepage.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Homepage', () => {
test('renders hero section with correct heading', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/ECOSIRE/);
const hero = page.getByTestId('hero-heading');
await expect(hero).toBeVisible();
await expect(hero).not.toBeEmpty();
});
test('navigation links are all accessible', async ({ page }) => {
await page.goto('/');
const navLinks = page.getByRole('navigation').getByRole('link');
const count = await navLinks.count();
for (let i = 0; i < count; i++) {
const link = navLinks.nth(i);
const href = await link.getAttribute('href');
// All nav links should have valid hrefs
expect(href).toBeTruthy();
expect(href).not.toBe('#');
}
});
test('CTA button links to services page', async ({ page }) => {
await page.goto('/');
await page.getByTestId('hero-cta').click();
await expect(page).toHaveURL(/\/services/);
});
});
i18n ٹیسٹ
// tests/public/i18n.spec.ts
import { test, expect } from '@playwright/test';
const testLocales = ['zh', 'es', 'ar', 'fr'];
test.describe('Internationalization', () => {
for (const locale of testLocales) {
test(`/${locale}/ renders with correct lang attribute`, async ({ page }) => {
await page.goto(`/${locale}/`);
const html = page.locator('html');
await expect(html).toHaveAttribute('lang', locale);
});
}
test('Arabic locale sets dir="rtl"', async ({ page }) => {
await page.goto('/ar/');
const html = page.locator('html');
await expect(html).toHaveAttribute('dir', 'rtl');
});
test('language switcher changes locale', async ({ page }) => {
await page.goto('/');
await page.getByTestId('language-switcher').click();
await page.getByTestId('locale-option-fr').click();
await expect(page).toHaveURL('/fr/');
await expect(page.locator('html')).toHaveAttribute('lang', 'fr');
});
test('hreflang tags are present on all pages', async ({ page }) => {
await page.goto('/');
const hreflangs = await page.locator('link[rel="alternate"][hreflang]').all();
// Should have 11 locale hreflangs + x-default
expect(hreflangs.length).toBeGreaterThanOrEqual(12);
});
});
قابل رسائی ٹیسٹ
// tests/public/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.describe('Accessibility', () => {
const pages = ['/', '/blog', '/services', '/about', '/contact'];
for (const path of pages) {
test(`${path} has no WCAG 2.1 AA violations`, async ({ page }) => {
await page.goto(path);
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.analyze();
if (results.violations.length > 0) {
const report = results.violations.map((v) =>
`${v.id}: ${v.description} — ${v.nodes.length} element(s)`
).join('\n');
expect.fail(`Accessibility violations on ${path}:\n${report}`);
}
});
}
test('skip link is the first focusable element', async ({ page }) => {
await page.goto('/');
await page.keyboard.press('Tab');
const focused = page.locator(':focus');
await expect(focused).toHaveAttribute('href', '#main-content');
});
});
CI انٹیگریشن
# .github/workflows/ci.yml (E2E section)
e2e-tests:
runs-on: ubuntu-latest
needs: [unit-tests, build]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Install Playwright browsers
run: cd apps/web && npx playwright install --with-deps chromium firefox
- name: Run E2E tests
run: cd apps/web && npx playwright test --project=chromium --project=firefox
env:
PLAYWRIGHT_BASE_URL: ${{ secrets.STAGING_URL }}
TEST_ADMIN_EMAIL: ${{ secrets.TEST_ADMIN_EMAIL }}
TEST_ADMIN_PASSWORD: ${{ secrets.TEST_ADMIN_PASSWORD }}
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: apps/web/playwright-report/
retention-days: 7
اکثر پوچھے گئے سوالات
مجھے data-testid بمقابلہ ARIA رولز بمقابلہ ٹیکسٹ سلیکٹر کب استعمال کرنا چاہئے؟
data-testid کو پرائمری سلیکٹر کے طور پر ترجیح دیں — یہ UI کو دوبارہ ڈیزائن کرنے، کاپی کی تبدیلیوں، اور ریفیکٹرز سے بچتا ہے۔ ایک ساتھ رسائی کی جانچ کے لیے ARIA رول سلیکٹرز (getByRole('button', { name: 'Submit' })) استعمال کریں۔ متنی مواد کے انتخاب کنندگان (getByText('Submit')) سے پرہیز کریں کیونکہ کاپی تبدیل ہونے پر وہ ٹوٹ جاتے ہیں۔ کبھی بھی CSS کلاس سلیکٹرز کا استعمال نہ کریں - وہ نفاذ کی تفصیلات ہیں جو اسٹائلنگ کے کام کے ساتھ بدل جاتی ہیں۔
مجھے CI میں کتنے پروجیکٹس (براؤزر) چلانے چاہئیں؟
پل کی درخواستوں کے لیے، صرف CI کو تیز رکھنے کے لیے Chromium چلائیں۔ مین برانچ انضمام کے لیے، تینوں ڈیسک ٹاپ براؤزرز (کرومیم، فائر فاکس، ویب کٹ) چلائیں۔ ریلیز امیدواروں کے لیے، موبائل ویو پورٹ شامل کریں۔ WebKit سفاری کے لیے مخصوص لے آؤٹ کیڑے پکڑتا ہے۔ فائر فاکس ایسے نرالا پن کو پکڑتا ہے جسے کروم اور سفاری دونوں چھپاتے ہیں۔ ریلیز سے پہلے کراس براؤزر کیڑے پکڑنے کے دوران ٹائرنگ CI منٹ بچاتا ہے۔
میں ٹائمنگ کے مسائل کی وجہ سے فلکی ٹیسٹوں کو کیسے ہینڈل کروں؟
پلے رائٹ کے آٹو ویٹ کو زیادہ تر ٹائمنگ مسائل کو ہینڈل کرنا چاہئے — page.waitForTimeout() کے بجائے await expect(locator).toBeVisible() استعمال کریں۔ اگر خودکار انتظار کافی نہیں ہے تو، نیٹ ورک پر منحصر حالت کے لیے page.waitForResponse()، بھاری صفحات کے لیے page.waitForLoadState('networkidle')، یا حسب ضرورت حالات کے لیے page.waitForFunction() استعمال کریں۔ CI میں retries: 2 سیٹ کریں تاکہ بنیادی ڈھانچے کی کمزوری سے حقیقی ناکامیوں کو الگ کیا جا سکے۔
کیا E2E ٹیسٹوں کو اصلی ڈیٹا بیس یا سیڈ ٹیسٹ ڈیٹا بیس کا استعمال کرنا چاہیے؟
E2E ٹیسٹوں کے لیے سیڈ ٹیسٹ ڈیٹا بیس کے ساتھ ایک وقف شدہ سٹیجنگ ماحول استعمال کریں۔ پیداوار کے خلاف E2E ٹیسٹ کبھی نہ چلائیں۔ CI کے لیے، ٹیسٹ ڈیٹا کے ساتھ تازہ ڈیٹا بیس کے ساتھ ڈوکر کمپوز اسٹیک کو گھمائیں۔ beforeAll/afterAll ہکس میں API کالز کے ذریعے ٹیسٹ ڈیٹا بنائیں اور صاف کریں بجائے اس کے کہ پہلے سے سیڈڈ ڈیٹا پر بھروسہ کریں جو بہہ سکتا ہے۔
میں پلے رائٹ میں فائل ڈاؤن لوڈز کی جانچ کیسے کروں؟
page.waitForDownload() پیٹرن استعمال کریں: const [download] = await Promise.all([page.waitForEvent('download'), page.click('[data-testid="download-button"]')]); const path = await download.path(); expect(path).toBeTruthy();۔ آپ download.suggestedFilename() کو بھی چیک کر سکتے ہیں اور توثیق کے لیے فائل کا مواد پڑھ سکتے ہیں۔
اگلے اقدامات
صارفین کو آپ کی درخواست دیکھنے سے پہلے پلے رائٹ E2E ٹیسٹ آخری حفاظتی جال ہیں۔ صحیح طریقے سے سرمایہ کاری کی گئی — صفحہ آبجیکٹ، ڈیٹا ٹیسٹڈ سلیکٹرز، اوتھ فکسچر، اور CI انضمام کے ساتھ — وہ دیکھتے ہیں کہ یونٹ ٹیسٹ میں کیا کمی آتی ہے اور آپ کو جمعہ کو تعینات کرنے کا اعتماد فراہم کرتے ہیں۔
ECOSIRE 29 پلے رائٹ E2E ٹیسٹ فائلوں کو برقرار رکھتا ہے جس میں عوامی صفحات پر 416+ ٹیسٹ کیسز، ایڈمن ورک فلو، اور تمام 11 لوکیلز کے لیے رسائی شامل ہے۔ ہماری فرنٹ اینڈ انجینئرنگ سروسز کو دریافت کریں یہ جاننے کے لیے کہ ہم کوالٹی گیٹڈ Next.js ایپلی کیشنز کیسے بناتے ہیں۔
تحریر
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
ECOSIRE کے ساتھ اپنا کاروبار بڑھائیں
ERP، ای کامرس، AI، تجزیات، اور آٹومیشن میں انٹرپرائز حل۔
متعلقہ مضامین
Accounts Payable Automation ROI: The Real Numbers Behind Cutting Invoice Costs From $12 to $2 (2026)
Accounts payable automation cuts invoice processing from $12-15 to under $3 each. The full 2026 ROI math: payback by volume, savings sources, and limits.
25 Business Process Automation Examples That Actually Work in 2026 (From a Team Running Them in Production)
25 real business process automation examples across finance, sales, support, and operations — with honest notes on what AI agents, RPA, and workflows do best.
GoHighLevel AI Employee in 2026: What It Does, Costs, and When to Use It
GoHighLevel AI Employee explained for 2026: Voice AI, Conversation AI, and Content AI capabilities, flat-rate vs usage pricing, limits, and when it pays.