أتمتة اختبار واجهة المستخدم
أتمتة اختبار واجهة المستخدم (UI test automation) تقود متصفحاً حقيقياً كما يفعل المستخدم — تعثر على العناصر وتنقر وتكتب وتقرأ ما على الشاشة — ثم تتحقق من أن النتيجة تطابق المتطلب. وهي النوع الوحيد من الاختبارات الآلية الذي يمرّ بالمكدّس كله دفعة واحدة: الترميز والأنماط وشيفرة العميل وواجهة API وقاعدة البيانات وما يربط بينها. وهذه قيمتها وكلفتها معاً: اختبارات الواجهة هي أبطأ الاختبارات وأغلاها وأكثرها عرضة للفشل في أي فريق.
هذا الدليل هو مدخل الموضوع. يغطي ما تجيده أتمتة الواجهة وما لا تجيده، وكيف تُبنى طبقات إطار عمل قابل للصيانة، والفرق بين Selenium/WebDriver وPlaywright وCypress، والتزامن والتأكيدات، وكائنات الصفحات والمكوّنات، وبيانات الاختبار، والتشغيل عبر المتصفحات وبالتوازي، وCI، وكيف تقرر ما ينتمي إلى مستوى الواجهة. ويتعمق دليلان أضيق في جانبين: استراتيجيات محددات العناصر، وتشخيص الاختبارات المتذبذبة وإصلاحها.
ما الغرض من أتمتة الواجهة — وفيمَ تضعف
يجيب اختبار الواجهة عن سؤال لا يجيب عنه اختبار آخر: هل يستطيع مستخدم، في متصفح حقيقي، إكمال هذه الرحلة من بدايتها إلى نهايتها؟ فهو يلتقط العيوب التي تعيش بين الطبقات — زر موصول بمعالج خاطئ، أو نموذج يرسل حقلاً ترفضه واجهة API، أو خطأ برمجي لا يظهر إلا بعد إعادة توجيه، أو تغيير في التخطيط يُخفي زر الإرسال خلف شريط ملفات تعريف الارتباط.
وهو ضعيف في كل ما عدا ذلك تقريباً. فالتحقق من كل قاعدة تحقّق أو فرع حسابي أو رمز خطأ عبر المتصفح بطيء، وعندما يفشل اختبار الواجهة فإنه يخبرك أن الرحلة تعطّلت، لا أين تعطّلت. هذه الفحوص مكانها اختبارات الوحدة (Unit tests) واختبارات API، التي تعمل في أجزاء من الثانية وتشير إلى الشيفرة المعيبة.
- مناسب — رحلات المستخدم الحرجة (التسجيل، وتسجيل الدخول، والبحث، وإتمام الشراء، وتأكيد الدفع)، وتكامل الواجهة الأمامية مع الخلفية، وسلوك مجموعة صغيرة من المسارات عبر المتصفحات، واختبار الانحدار (Regression) لمسارات تعطّلت من قبل.
- غير مناسب — قواعد العمل ذات التركيبات الكثيرة من المدخلات، ومصفوفات التحقق من المدخلات، والحكم على التصميم المرئي، وقابلية الاستخدام، والأسئلة الاستكشافية («هل هذا مربك؟»)، والشاشات التي يتغير تصميمها كل سبرنت.
- علامات الإنذار — حزمة يستغرق تشغيلها أطول مما يقبل الفريق انتظاره، واختبارات تفشل لأسباب لا علاقة لها بالمنتج، وإخفاقات لا يحقق فيها أحد لأنها «على الأرجح متذبذبة».
كيف تُبنى طبقات إطار أتمتة قابل للصيانة
معظم حزم الواجهة التي تصبح غير قابلة للصيانة تفشل للسبب نفسه: نيّة الاختبار وبنية الصفحة واستدعاءات الأداة مختلطة في الدالة نفسها، فيكسر تغيير واحد في الترميز خمسين اختباراً. والعلاج هو الفصل إلى طبقات، لكل منها سبب واحد للتغيير. يصف منهج ISTQB للمستوى المتقدم في هندسة أتمتة الاختبار (CTAL-TAE) بنية عامة لأتمتة الاختبار تقوم على الفكرة نفسها — فصل ما يُختبر عن طريقة قيادة النظام قيد الاختبار. وأسماء الطبقات أدناه هي التي تستخدمها معظم الفرق يومياً.
- طبقة الاختبارات — المواصفات نفسها. سيناريو واحد لكل اختبار، مكتوب بلغة العمل («العميل يدفع ببطاقة محفوظة»). لا محددات ولا انتظار ولا استدعاءات للمشغّل.
- كائنات الصفحات والمكوّنات (Page/Component Objects) أو تجريد مكافئ مثل مهام نمط Screenplay — المكان الوحيد الذي يعرف بنية الشاشة والمحددات التي تستخدمها.
- طبقة المشغّل/إطار العمل — روابط WebDriver أو Playwright أو Cypress، مع أغلفة رقيقة لأعرافكم الخاصة (fixtures وأوامر مخصصة). أبقِ الأغلفة رقيقة: إعادة بناء إطار العمل تُخفي ميزاته وتوثيقه.
- طبقة بيانات الاختبار — مصانع وعملاء API تُنشئ المستخدمين والطلبات والمنتجات التي يحتاجها الاختبار، وتحذفها بعده.
- الإعدادات (Configuration) — عناوين URL الأساسية والمتصفحات والمهل وبيانات الاعتماد وأعلام الميزات لكل بيئة، تُقرأ من متغيرات البيئة أو ملفات الإعداد، ولا تُكتب أبداً داخل الاختبارات.
- التقارير — نجاح/فشل لكل اختبار مع الأدلة اللازمة لتشخيص الفشل: لقطات الشاشة ومقاطع الفيديو والآثار (traces) وسجلات وحدة التحكم والشبكة، ينشرها خط CI.
e2e/
tests/ checkout.spec.ts, login.spec.ts <- test layer (intent only)
pages/ login-page.ts, checkout-page.ts <- page objects
components/ header.ts, date-picker.ts <- component objects
api/ orders-client.ts, users-client.ts <- set up state via API
fixtures/ auth.ts, test-data.ts <- data + setup/teardown
config/ playwright.config.ts, env/staging.env <- configuration
reports/ (generated) html report, traces, junit.xml <- reportingالاستدعاءات تنزل عبر الطبقات
- 1طبقة الاختباراتسيناريو واحد لكل اختبار بلغة العمل؛ والاختبار هو من يقرر ما الصحيح
تتغير حين يتغير السيناريو
- 2كائنات الصفحات والمكوّناتأو تجريد مكافئ مثل مهام Screenplay: المكان الوحيد الذي يعرف بنية الشاشة والمحددات
تتغير حين يتغير الترميز: صنف واحد بدلاً من كل الاختبارات
- 3طبقة المشغّل/إطار العملروابط WebDriver أو Playwright أو Cypress، مع أغلفة رقيقة (fixtures وأوامر مخصصة)
تتغير حين تتغير أداة الأتمتة
- 4المتصفح والنظام قيد الاختباريُقاد عبر الطبقة التي فوقه فقط
من طبقة الاختبارات إلى طبقة المشغّل/إطار العمل:غير مسموح: محددات أو انتظار أو استدعاءات للمشغّل داخل اختبار
بجانب مسار الاستدعاءات: طبقة بيانات الاختبار في api/ وfixtures/ (تُنشئ ما يحتاجه الاختبار وتحذفه بعده)، والإعدادات لكل بيئة في config/ (لا تُكتب داخل الاختبارات)، والتقارير في reports/ (ينشرها خط CI).
أتمتة المتصفح: WebDriver وPlaywright وCypress
WebDriver مواصفة من W3C للتحكم في المتصفح عن بُعد: ترسل مكتبة عميل أوامر عبر HTTP إلى مشغّل خاص بكل متصفح (chromedriver وgeckodriver وsafaridriver وغيرها)، والمشغّل يتحكم في المتصفح. وSelenium هو أشهر عميل لها، بروابط رسمية للغات Java وPython وC# وJavaScript وRuby، ويوزّع Selenium Grid الجلسات على عدة أجهزة. ولأن مصنّعي المتصفحات يوفّرون مشغّلاتهم بأنفسهم فإن WebDriver هو الخيار الأكثر قابلية للنقل، كما يقوم عليه البروتوكول الذي تستخدمه أدوات أخرى مثل Appium لتطبيقات الجوال.
Playwright مكتبة مفتوحة المصدر من Microsoft تقود Chromium وFirefox وWebKit عبر اتصالها الخاص بالمتصفح لا عبر WebDriver. يوفّر مشغّل اختباراتها الانتظار التلقائي قبل الإجراءات، والتأكيدات التي تعيد المحاولة (web-first)، وسياقات متصفح معزولة، وعمّالاً متوازيين، واعتراض الشبكة، وعارضاً للآثار (Trace Viewer). وتدعم TypeScript/JavaScript وPython وJava و.NET.
Cypress يشغّل شيفرة الاختبار داخل المتصفح بجوار التطبيق، ما يمنحه وصولاً مباشراً إلى الصفحة والشبكة، وإعادة محاولة تلقائية للاستعلامات والتأكيدات، ومشغّلاً تفاعلياً يعرض كل أمر على لقطة من DOM. تُكتب الاختبارات بـ JavaScript أو TypeScript. ويجلب هذا التصميم داخل المتصفح مقايضات يذكرها توثيقه، مثل ما يتعلق بتعدد علامات التبويب والتنقل بين النطاقات المختلفة (cross-origin).
لا أحد من الثلاثة هو «الأفضل». اختر حسب اللغة التي يكتب بها فريقك، والمتصفحات التي يستخدمها مستخدموك فعلاً، وحاجتك إلى شبكة أجهزة أو بنية للجوال، ومدى وضوح تقارير الأداة عند الفشل. والمفاهيم في بقية هذا الدليل تنطبق عليها جميعاً.
المحددات: كيف تعثر الاختبارات على العناصر
محدِّد العنصر (Locator) هو الاستعلام الذي يستخدمه الاختبار للعثور على عنصر، وجودته تحدد معظم كلفة صيانة الحزمة. يوصي توثيق Playwright وTesting Library بالسمات التي يراها المستخدم أولاً — دور العنصر (role) واسمه الميسَّر (accessible name) وتسميته (label) ونصه المرئي — ثم معرّف اختبار مخصص (test id)؛ وبإضافة CSS ثم XPath كملاذ أخير نحصل على ترتيب يصلح لأي أداة.
تصمد المحددات المبنية على ما يراه المستخدم أمام إعادة الهيكلة لأنها لا تتغير إلا إذا تغيّرت الواجهة التي يراها المستخدم، وتفشل عندما يفقد العنصر اسمه الميسَّر — وهذا فحص جانبي مفيد لإمكانية الوصول. ويغطي دليل استراتيجيات المحددات الترتيب كاملاً، والمقارنة بين CSS وXPath، والمعرّفات الديناميكية، وإطارات iframe، وShadow DOM، والأنماط الخاطئة مثل XPath المطلق والمحددات المعتمدة على الفهرس.
// Playwright — in order of preference
page.getByRole("button", { name: "Place order" })
page.getByLabel("Email")
page.getByText("Order confirmed")
page.getByTestId("order-total")
page.locator("form#checkout button[type=submit]") // CSS: only when nothing above fits
// Selenium (Java)
driver.findElement(By.cssSelector("[data-testid=order-total]"));
// Cypress
cy.get("[data-cy=order-total]");الانتظار والتزامن: لا انتظار ثابت أبداً
جزء كبير من تذبذب اختبارات الواجهة سباق: يتصرف الاختبار أو يتحقق قبل أن يصبح التطبيق جاهزاً. والانتظار الثابت (Thread.sleep أو time.sleep أو cy.wait(5000)) «يصلح» المشكلة على حاسوبك بالتخمين، ثم يفشل على خادم CI أبطأ — ويهدر مدته كاملة في كل تشغيل سريع. انتظر شرطاً بدلاً من ذلك، مع مهلة قصوى.
- الانتظار الضمني في Selenium (Implicit wait) — إعداد على مستوى المشغّل يجعل كل بحث عن عنصر يعيد المحاولة حتى يصبح العنصر موجوداً في DOM أو تنتهي المهلة. وهو يغطي الوجود فقط، لا الظهور ولا القابلية للنقر.
- الانتظار الصريح في Selenium (Explicit wait) — WebDriverWait مع شرط (ظاهر، قابل للنقر، نص موجود، أو دالتك الخاصة) يُطبَّق حيث يلزم بالضبط. ويحذّر توثيق Selenium من الخلط بين الانتظار الضمني والصريح لأن المهل المجتمعة تصبح غير متوقعة.
- الانتظار التلقائي في Playwright (Auto-waiting) — قبل إجراء مثل click أو fill يتحقق Playwright من أن العنصر قابل للتفاعل (ظاهر، ومستقر، ويستقبل الأحداث، ومفعّل، وقابل للتحرير في حقول الإدخال) وينتظر حتى يصبح كذلك أو تنتهي المهلة.
- قابلية إعادة المحاولة في Cypress (Retry-ability) — تُعاد محاولة الاستعلامات مثل cy.get وcy.contains مع التأكيد المرتبط بها حتى ينجح التأكيد أو تنتهي مهلة الأمر. أما الإجراءات مثل .click() فلا تُعاد، لذا تحقّق من الحالة التي تحتاجها قبل التصرف.
- انتظر إشارات التطبيق نفسه — استجابة شبكة محددة، أو اختفاء مؤشر التحميل، أو حالة «تم الحفظ» — بدلاً من انتظار الوقت أو حالة عامة مثل «خمول الشبكة».
// Selenium (Java) — explicit wait for a condition, not a sleep
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement status = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid=order-status]")));
// Playwright (TypeScript) — click auto-waits; the assertion retries
await page.getByRole("button", { name: "Pay now" }).click();
await expect(page.getByTestId("order-status")).toHaveText("Paid");
// Cypress — the query retries until the assertion passes or times out
cy.get("[data-cy=order-status]").should("have.text", "Paid");
// Never: Thread.sleep(5000) / time.sleep(5) / cy.wait(5000)التأكيدات: تحقّق مما يراه المستخدم، ودعها تعيد المحاولة
ينبغي أن يصف التأكيد (Assertion) نتيجة تهم المستخدم أو العمل: عنوان التأكيد ظاهر، وإجمالي الطلب 199.20 ريال، ورسالة الخطأ تسمّي الحقل غير الصالح، وعنوان URL هو صفحة الطلب. أما التأكيد على تفاصيل داخلية — صنف CSS أو عدد عناصر div أو حالة مكوّن — فيربط الاختبار بالتنفيذ وينكسر عند إعادة هيكلة غير ضارة.
استخدم التأكيدات التي تعيد المحاولة حيث يوفرها إطار العمل. في Playwright تعيد expect(locator).toBeVisible() وtoHaveText() وexpect(page).toHaveURL() المحاولة حتى تنجح أو تنتهي المهلة، بينما expect(await locator.textContent()).toBe(…) تقرأ مرة واحدة وتتسابق مع الصفحة. وفي Cypress تعيد .should() محاولة الاستعلام الذي قبلها. وفي Selenium انتظر الشرط صراحةً ثم تحقّق.
- تحقّق من سلوك واحد في كل اختبار، بعدد التأكيدات الذي يلزم لإثبات ذلك السلوك.
- استخدم قيماً دقيقة للمبالغ والأعداد والحالات، والأنماط فقط للأجزاء المتغيرة فعلاً، مثل رقم طلب بصيغة ORD-2026-000184 أو طابع زمني.
- عندما تعرض الواجهة بيانات من API فتحقّق من النتيجة المعروضة في اختبار الواجهة ومن الاستجابة كاملة في اختبار API — لا تُعد التحقق من كل حقل JSON عبر المتصفح.
- اجعل رسائل الفشل تُقرأ كعناوين أخطاء: المتوقع «Paid»، والفعلي «Pending» للطلب ORD-2026-000184.
كائنات الصفحات وكائنات المكوّنات
كائن الصفحة (Page Object) صنف يمثّل صفحة أو شاشة واحدة. يعرض ما يستطيع المستخدم فعله فيها كدوال (signIn وaddToCart) ويُخفي المحددات وتفاصيل التفاعل. وعندما يتغير الترميز تُصلح صنفاً واحداً بدلاً من كل الاختبارات. يصف توثيق Selenium هذا النمط ويوصي بأن تعيد دوال كائن الصفحة كائنات صفحات أخرى أو بيانات، وألّا تحتوي كائنات الصفحات على تأكيدات — فالاختبار هو من يقرر ما الصحيح.
وتطبّق كائنات المكوّنات (Component Objects) الفكرة نفسها على الأجزاء التي تظهر في صفحات كثيرة: الترويسة، ومنتقي التاريخ، وجدول البيانات، والنافذة الحوارية. فيتكوّن كائن صفحة إتمام الشراء من مكوّن الترويسة ومكوّن نموذج العنوان بدلاً من تكرار محدداتهما.
أبقِ كائنات الصفحات صغيرة ومركّزة على السلوك. فوجود «BasePage» من 900 سطر مليء بالدوال العامة علامة على أن التجريد تحوّل إلى إطار عمل ثانٍ. وتفضّل بعض الفرق بدائل مثل نمط Screenplay (ممثلون ينفذون مهام) أو دوال مساعدة بسيطة بلغة المجال؛ ولا بأس بها ما دامت تحقق الفصل نفسه. يستخدم المثال أدناه Playwright مع TypeScript.
// pages/orders-page.ts
import type { Page, Locator } from "@playwright/test";
export class OrdersPage {
readonly heading: Locator;
constructor(page: Page) {
this.heading = page.getByRole("heading", { name: "My orders" });
}
}
// pages/login-page.ts
import type { Page, Locator } from "@playwright/test";
import { OrdersPage } from "./orders-page";
export class LoginPage {
readonly email: Locator;
readonly password: Locator;
readonly submit: Locator;
constructor(private readonly page: Page) {
this.email = page.getByLabel("Email");
this.password = page.getByLabel("Password");
this.submit = page.getByRole("button", { name: "Sign in" });
}
async goto() {
await this.page.goto("/login");
}
async signIn(email: string, password: string): Promise<OrdersPage> {
await this.email.fill(email);
await this.password.fill(password);
await this.submit.click();
return new OrdersPage(this.page);
}
}
// tests/login.spec.ts
import { test, expect } from "@playwright/test";
import { LoginPage } from "../pages/login-page";
test("customer sees their orders after signing in", async ({ page }) => {
const login = new LoginPage(page);
await login.goto();
const orders = await login.signIn("layla.h@example.com", process.env.TEST_USER_PASSWORD!);
await expect(orders.heading).toBeVisible();
await expect(page).toHaveURL(/\/orders$/);
});بيانات الاختبار والعزل
يجب أن يُنشئ كل اختبار واجهة ما يحتاجه وألّا يعتمد على شيء أنشأه اختبار آخر. فالحسابات والطلبات المشتركة هي السبب الأكثر شيوعاً لاختبارات تنجح منفردة وتفشل ضمن الحزمة: اختبار يلغي الطلب الذي يتحقق منه اختبار آخر، أو اختباران متوازيان يعدّلان الملف الشخصي نفسه.
أنشئ الحالة عبر API أو بذر قاعدة البيانات (seed)، لا عبر الواجهة. فتسجيل الدخول عبر النموذج في كل اختبار، أو ملء السلة بالنقر عبر عشر صفحات منتجات، يجعل كل اختبار بطيئاً ويعرّضه لإخفاقات في شاشات لا يختبرها. اختبر نموذج تسجيل الدخول مرة واحدة في اختباره الخاص؛ وفي غيره احصل على جلسة عبر API وأعد استخدامها — إذ يستطيع Playwright حفظ حالة المتصفح المصادَق عليه في ملف، ويوفّر Cypress الأمر cy.session للغرض نفسه.
- بيانات فريدة لكل اختبار — أضف معرّف تشغيل أو UUID إلى البريد والأسماء (مثلاً layla.h+run8412@example.com) حتى لا تتصادم التشغيلات المتوازية.
- بيانات خيالية فقط — لا تنسخ عملاء الإنتاج إلى بيئات الاختبار أبداً؛ ولّد سجلات واقعية بدلاً من ذلك.
- استبدل ما لا تملكه — يمكن استبدال بوابات الدفع ومزودي الرسائل النصية وخدمات الخرائط باستجابات وهمية في معظم اختبارات الواجهة (page.route في Playwright وcy.intercept في Cypress)، مع الإبقاء على فحص منفصل واحد مقابل بيئة الاختبار الحقيقية لدى المزوّد.
- نظّف، أو اجعل البيانات قابلة للرمي — احذف السجلات المُنشأة بعد الاختبار، أو استخدم بيئة يُعاد ضبطها في كل تشغيل.
جرّبها: منشئ طلبات API جرّبها: مولّد مستخدمين وهميين جرّبها: مصنع بيانات الاختبار جرّبها: مولّد استجابات API وهمية
الاختبار عبر المتصفحات والتنفيذ المتوازي
يتحقق الاختبار عبر المتصفحات (Cross-browser testing) من أن الرحلات نفسها تعمل في محركات المتصفحات التي يستخدمها مستخدموك فعلاً: المتصفحات المبنية على Chromium (Chrome وEdge)، وFirefox، وSafari (WebKit)، بعروض سطح المكتب والجوال. دع بيانات التحليلات تحدد المصفوفة؛ فتشغيل كل اختبار على كل متصفح يضاعف الكلفة بفائدة قليلة. والتقسيم الشائع هو الحزمة كاملة على متصفح واحد، ومجموعة دخان (smoke) من الرحلات الحرجة على البقية.
نسخة WebKit في Playwright ليست المنتج نفسه الذي هو Safari على macOS أو iOS. فإن كان مستخدمو Safari مهمين لك، فأضف تشغيلات على Safari الحقيقي — عبر safaridriver أو سحابة أجهزة — لأهم المسارات.
التنفيذ المتوازي هو ما يُبقي الحزمة المتنامية سريعة، ولا ينجح إلا إذا كانت الاختبارات معزولة. تقسّم المشغّلات الاختبارات على عمّال: عمّال Playwright والتجزئة (sharding)، وعُقد Selenium Grid مع توازي TestNG أو JUnit أو pytest-xdist، وملفات مواصفات Cypress الموزّعة على أجهزة CI. ويحتاج كل عامل إلى سياق متصفح خاص وبيانات خاصة.
- صمّم لأي ترتيب — شغّل الحزمة أحياناً بترتيب عشوائي لتثبت أنه لا توجد اعتماديات خفية بين الاختبارات.
- جزّئ الحزم الطويلة على عدة مهام CI ثم ادمج التقارير في تقرير واحد.
- راقب الموارد المشتركة — مستأجر اختبار واحد، أو API محدود المعدل، أو صندوق بريد واحد قد يحوّل الحزمة «المتوازية» إلى تسلسلية أو يجعلها متذبذبة.
تشغيل اختبارات الواجهة في CI
في CI تعمل المتصفحات دون واجهة رسومية (Headless) على خوادم نظيفة، غالباً داخل حاويات مثبّت فيها المتصفحات مسبقاً. ثبّت إصدارات المتصفحات وإطار العمل حتى لا يغيّر تحديثُ صورة الخادم النتائج بصمت، وسجّل إصدار التطبيق قيد الاختبار في التقرير.
لا يفيد تشغيل CI الفاشل إلا إذا استطاع أحد تشخيصه دون إعادة تشغيله محلياً. اضبط الحزمة لتحتفظ بالأدلة عند الفشل — لقطات الشاشة ومقاطع الفيديو وآثار Playwright وسجلات وحدة التحكم والشبكة — وانشر تقرير HTML وملف JUnit XML كمخرجات بناء (artifacts) ليعرض الخط النتائج لكل اختبار. المثال مهمة GitHub Actions لمجموعة دخان في Playwright؛ وخطوط GitLab CI وJenkins تتبع الشكل نفسه.
- طلب الدمج (Pull request) — مجموعة دخان سريعة من رحلات الواجهة (دقائق لا ساعة) إلى جانب اختبارات الوحدة وAPI.
- الفرع الرئيسي أو التشغيل الليلي — انحدار الواجهة الكامل عبر مصفوفة المتصفحات.
- بعد النشر — عدد قليل من الرحلات القرائية فقط على البيئة الحية.
- الفشل يمنع الدمج. والاختبارات المتذبذبة تُصلَح أو تُحجَر (Quarantine) مع مالك محدد، ولا يُعاد تشغيلها حتى تخضرّ؛ ويغطي دليل الاختبارات المتذبذبة التشخيص وإعادة المحاولة والحجر.
name: ui-smoke
on: [pull_request]
jobs:
ui-smoke:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test --grep @smoke
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/استراتيجية عملية: ما الذي يُؤتمت على مستوى الواجهة
يضع هرم الاختبار (Test pyramid)، المشروح في دليل الاختبار اليدوي مقابل الآلي، كثيراً من اختبارات الوحدة السريعة في القاعدة، وعدداً أقل من اختبارات API والخدمات في الوسط، وعدداً صغيراً من اختبارات الواجهة في القمة. والقاعدة العملية لأتمتة الواجهة: أتمت رحلة عبر الواجهة فقط عندما يكون المعرَّض للخطر هو الرحلة نفسها — الربط بين الشاشات والواجهة الأمامية والخلفية — وادفع كل فحص آخر إلى أدنى مستوى قادر على اكتشافه.
مثلاً، حساب خصم له اثنتا عشرة تركيبة من القواعد مكانه اختبارات الوحدة وAPI؛ أما «يظهر الإجمالي بعد الخصم في صفحة إتمام الشراء ويُخصم عند الدفع» فمكانه اختبار واجهة واحد.
- ابدأ من المخاطر: اسرد الرحلات التي يكلّف فشلها مالاً أو بيانات أو ثقة (الدفع، والتسجيل، وتسجيل الدخول، والبحث الأساسي) وأتمتها أولاً.
- اكتب كل رحلة أولاً كمعايير قبول بصيغة Given/When/Then؛ فتصبح المعايير أسماء الاختبارات وتأكيداتها.
- ضع ميزانية للحزمة: اتفقوا على مدة قصوى لمجموعة الدخان في طلب الدمج وللتشغيل الكامل، واحذف الاختبارات أو ادفعها إلى مستوى أدنى عند تجاوزها.
- عامل الاختبارات كشيفرة إنتاج: مراجعة شيفرة، وأعراف تسمية، ولا نسخ ولصق، ومالك مسمّى لكل منطقة.
- صُن باستمرار: حدّث كائنات الصفحات في طلب الدمج نفسه الذي يغيّر الواجهة، واتفق مع المطورين على معرّفات الاختبار، واحذف اختبارات الميزات التي لم تعد موجودة، ورقِّ إصدارات إطار العمل والمتصفحات في تغيير مستقل.
- قِس ما يهم: زمن الحصول على النتيجة، ومعدل التذبذب، والعيوب التي التقطتها الحزمة قبل الإصدار — لا العدد الخام للاختبارات.
الأسئلة الشائعة
ما أتمتة اختبار واجهة المستخدم؟
هي استخدام أداة مثل Selenium أو Playwright أو Cypress لقيادة متصفح حقيقي عبر رحلات المستخدم والتحقق من النتائج المرئية آلياً. وهي تتحقق من أن الواجهة الأمامية والخلفية والتكاملات تعمل معاً، وتكمّل اختبارات الوحدة وAPI الأسرع.
هل نختار Selenium أم Playwright أم Cypress؟
اختر حسب لغة فريقك، والمتصفحات التي يجب دعمها، وبنيتك التحتية (شبكة أجهزة، أو سحابة أجهزة، أو جوال)، ومدى جودة تقارير الأداة عند الفشل. يمكن للثلاثة دعم حزمة قابلة للصيانة؛ وتصميم إطار العمل وانضباط بيانات الاختبار أهم من الأداة.
كم اختبار واجهة ينبغي أن نملك؟
لا يوجد رقم صحيح. غطِّ الرحلات الحرجة ونقاط التكامل التي لا يفحصها إلا المتصفح، وأبقِ الباقي على مستوى API والوحدة. وإذا كانت الحزمة أبطأ من أن تعمل مع كل طلب دمج فهي كبيرة جداً أو مقسّمة بشكل سيئ.
لماذا تفشل اختبارات الواجهة لدينا عشوائياً؟
الأسباب المعتادة هي التوقيت (التصرف قبل جاهزية الصفحة)، والبيانات المشتركة بين الاختبارات، والبيئات غير المستقرة، وخدمات الأطراف الثالثة، والحركات الانتقالية. استبدل الانتظار الثابت بانتظار مشروط وتأكيدات تعيد المحاولة، واعزل البيانات، واستخدم الآثار ولقطات الشاشة لإيجاد السبب الفعلي.
هل يجب أن تحتوي كائنات الصفحات على تأكيدات؟
عموماً لا. يوصي توثيق Selenium بإبقاء التأكيدات في الاختبارات حتى تبقى كائنات الصفحات قابلة لإعادة الاستخدام. يمكن لكائن الصفحة التحقق من أنه وصل إلى الصفحة المتوقعة، لكن التحقق من السلوك مكانه الاختبار.
هل تغني الاختبارات المسجَّلة عن إطار العمل؟
أدوات التسجيل مثل Playwright codegen وSelenium IDE مفيدة لفهم الصفحة وصياغة المحددات أولياً. لكن النصوص المسجَّلة تحتاج عادةً إلى إعادة هيكلة في كائنات صفحات، ومحددات أفضل، وانتظار صحيح قبل أن تصبح قابلة للصيانة في حزمة طويلة العمر.
هل يجب تشغيل اختبارات الواجهة دون واجهة رسومية في CI؟
الوضع دون واجهة (Headless) هو المعتاد في CI لأنه أسرع ولا يحتاج إلى شاشة، وتتصرف المتصفحات الحديثة فيه بالطريقة نفسها لمعظم الأغراض. احتفظ بلقطات الشاشة أو الفيديو أو الآثار حتى تظل الإخفاقات مرئية.
المراجع
- Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0منهج ISTQB المتقدم لتصميم حلول أتمتة الاختبار وتنفيذها وصيانتها وبنيتها.
- WebDriver (W3C)بروتوكول W3C الذي تنفّذه المتصفحات لتتحكم فيها أدوات الأتمتة مثل Selenium عن بُعد (الإصدار Level 2 مسودة عمل).
- Selenium Documentationالتوثيق الرسمي لأتمتة المتصفح عبر WebDriver، بما فيه الانتظار والمحدِّدات وGrid.
- Playwright Documentationالتوثيق الرسمي لـ Playwright، إطار الاختبار الشامل الذي يشغّل Chromium وWebKit وFirefox.
- Cypress Documentationالتوثيق الرسمي لاختبار الويب الشامل واختبار المكونات في المتصفح باستخدام Cypress.
- Best Practices — Playwrightاختبر السلوك الذي يراه المستخدم، واجعل الاختبارات معزولة، وفضّل المحدِّدات المتينة والتأكيدات التي تنتظر، واستخدم التتبّع لتصحيح الأخطاء.
- Page object models — Seleniumنمط كائن الصفحة (Page Object): صنف لكل صفحة أو مكوّن يخفي المحدِّدات ويعرض الإجراءات التي يحتاجها الاختبار، فيقل التكرار.
- The Practical Test Pyramid (Ham Vocke)شرح واسع الاستشهاد لكيفية الموازنة بين اختبارات الوحدة والتكامل والعقود والواجهة في محفظة الاختبارات الآلية.
تمرين عملي
راجع اختبار إتمام شراء مسجَّلاً وخطّط حزمة الواجهة
أنت مهندس الجودة في متجر أثاث إلكتروني (مثال خيالي). يضيف الإصدار 3.4 خيار «الدفع ببطاقة محفوظة» في إتمام الشراء ويُطلَق بعد ثلاثة أسابيع. سجّلت نادية، وهي مهندسة أتمتة انضمت قبل شهرين، أول اختبار Playwright لإتمام الشراء بأداة codegen ثم عدّلته وفتحت طلب دمج (Pull request)؛ وكتبت أيضاً خطة لبقية الإصدار. وفي الأسبوع الماضي فشل 3 من 11 تشغيلاً لطلبات الدمج في CI ونجحت على حاسوبها. تعرض المادة الفريق، وتحليلات المتصفحات، وواجهة API في بيئة Staging التي يمكن للحزمة استخدامها، والاختبار مع أرقام الأسطر، والخطة.
اعمل على الورق بأقسام الدليل عن الطبقات والمحددات والانتظار والتأكيدات وبيانات الاختبار والتشغيل عبر المتصفحات وCI والاستراتيجية. أشِر إلى أرقام الأسطر، واكتب ما تجده بصيغة تعليقات مراجعة تستطيع نادية العمل بها. خصّص لذلك 45–60 دقيقة.
متجر أثاث إلكتروني (مثال خيالي) - متجر ويب، بيئة Staging: https://staging.furnish.example
يضيف الإصدار 3.4 خيار «الدفع ببطاقة محفوظة» في إتمام الشراء. الإطلاق بعد ثلاثة أسابيع.
الفريق: نادية (مهندسة أتمتة، انضمت قبل شهرين)، وعمر (الواجهة الأمامية)،
وسارة (الواجهة الخلفية)، ويوسف (مدير الإصدار)، والجودة: أنت.
التحليلات لآخر 90 يوماً (خيالية): متصفحات Chromium (Chrome وEdge) 74%،
وSafari 21% (iOS 17% وmacOS 4%)، وFirefox 4%، وغيرها 1%.
واجهة API في Staging
POST /api/auth/session تسجيل الدخول؛ يضبط ملف تعريف ارتباط الجلسة
POST /api/cart/items { sku, qty } يضيف صنفاً إلى سلة المستخدم المسجَّل
POST /api/test-support/customers لبيئات الاختبار فقط: ينشئ عميلاً قابلاً للحذف،
مع بطاقة محفوظة اختيارياً؛ ويعيد id وemail وpassword
DELETE /api/test-support/customers/{id}
تستدعي صفحة إتمام الشراء بيئة sandbox لبوابة الدفع مباشرةً من المتصفح:
POST https://sandbox.paygate.example/v1/charges (من 2 إلى 9 ثوانٍ؛ ترفض نحو 1 من كل 20)
نموذج عنوان إتمام الشراء: 7 حقول و23 قاعدة تحقق (إلزامي، وصيغة، وطول)،
تُفحص في المتصفح ثم مجدداً عبر POST /api/addresses.
أول اختبار كتبته نادية، مسجَّل بـ codegen ثم عُدِّل:
tests/checkout.spec.ts
1 import { test, expect } from '@playwright/test';
2
3 test('test 1', async ({ page }) => {
4 await page.goto('https://staging.furnish.example/login');
5 await page.locator('#email').fill('qa.shared@example.com');
6 await page.locator('#password').fill('Furnish2026!');
7 await page.locator('.MuiButton-root.css-1x2y3z').click();
8 await page.waitForTimeout(3000);
9 await page.goto('https://staging.furnish.example/p/oak-desk-140');
10 await page.locator('xpath=//div[3]/div[2]/button').click();
11 await page.goto('https://staging.furnish.example/p/desk-lamp-led');
12 await page.locator('xpath=//div[3]/div[2]/button').click();
13 await page.goto('https://staging.furnish.example/cart');
14 expect(await page.locator('.total').textContent()).toBe('1,249.00 SAR');
15 await page.locator('text=Checkout').click();
16 await page.locator('input').nth(3).fill('');
17 await page.locator('text=Continue').click();
18 expect(await page.locator('.error').count()).toBe(1);
19 await page.locator('input').nth(3).fill('Riyadh');
20 await page.locator('text=Continue').click();
21 await page.locator('text=Pay now').click();
22 await page.waitForTimeout(8000); // the gateway sandbox is slow
23 await expect(page.locator('.alert')).toHaveClass(/success/);
24 });
خطة نادية للإصدار 3.4
1. اختبار مثل هذا لكل قاعدة من قواعد التحقق الـ 23 في نموذج العنوان.
2. تشغيل الحزمة كاملة على Chromium وFirefox وWebKit وEdge مع كل طلب دمج.
3. إيقاف لقطات الشاشة والفيديو والآثار في CI لإبقاء المهمة سريعة؛ وإعادة التشغيل
محلياً لتشخيص أي فشل.
الأسبوع الماضي: فشل 3 من 11 تشغيلاً لطلبات الدمج في CI ونجحت على حاسوب نادية.- راجع الاختبار مقابل الدليل سطراً سطراً. لكل مشكلة اكتب أرقام الأسطر، والقاعدة التي تخالفها (الطبقات، أو المحددات، أو الانتظار، أو التأكيدات، أو بيانات الاختبار والعزل، أو الإعدادات)، وما علاقة تشغيلات CI الثلاثة الفاشلة بها على الأرجح.
- قرّر ما ينتمي إلى مستوى الواجهة. أين تُختبر قواعد التحقق الـ 23 (الوحدة أم API أم الواجهة)؟ ثم اكتب رحلات الواجهة التي تحتاجها الحزمة للإصدار 3.4 بصيغة Given/When/Then، ولكل رحلة تأكيداتها.
- صمّم الطبقات: مجلدات الحزمة وملفاتها لهذه الرحلة (الاختبارات، وكائنات الصفحات والمكوّنات، وعملاء API وfixtures، والإعدادات، والتقارير)، وما يحويه كل منها وما يجب ألا يحويه أبداً.
- أعد كتابة رحلة إتمام الشراء اختباراً في طبقة الاختبارات، بلغة TypeScript مع Playwright أو كقائمة مرقّمة بما يفعله: الحالة تُهيَّأ عبر API، وبوابة الدفع مستبدَلة باستجابة وهمية (stub)، والمحددات مبنية على ما يراه المستخدم، والتأكيدات تعيد المحاولة، ولا يبقى أثر بعد الاختبار.
- خطّط التشغيل: مصفوفة المتصفحات وفق التحليلات المعطاة؛ وما يعمل مع طلب الدمج، وعلى الفرع الرئيسي أو ليلياً، وبعد النشر؛ والأدلة التي يحتفظ بها CI عند الفشل؛ وردّك على النقاط الثلاث في خطة نادية.
إظهار الإجابة النموذجية
المراجعة، الجزء الأول: البنية والبيانات. السطر 3: الاسم «test 1» لا يقول شيئاً، والاختبار يتحقق من أربعة سلوكيات — تسجيل الدخول، وإجمالي السلة، والتحقق من العنوان، والدفع — فعندما يفشل لا يعرف أحد أي رحلة تعطّلت؛ سلوك واحد لكل اختبار، مسمّى بلغة العمل. الأسطر 4 و9 و11 و13: عنوان URL الأساسي مكتوب داخل الاختبار. مكانه طبقة الإعدادات (baseURL في playwright.config.ts، يُقرأ من متغير بيئة مثل BASE_URL) حتى تعمل الحزمة نفسها على أي بيئة. السطران 5–6: حساب مشترك وكلمة مروره في الشيفرة. مكان كلمة المرور سرٌّ محفوظ (process.env.TEST_USER_PASSWORD في مثال الدليل)، والحساب المشترك يكسر العزل: تشغيلان في الوقت نفسه — خطّا CI لطلبَي دمج مختلفين اليوم، وعاملان متوازيان حين تكبر الحزمة — يضيفان أصنافاً إلى السلة نفسها على الخادم، فيكون الإجمالي في السطر 14 خاطئاً لأحدهما. ويتوقع السطر 18 خطأً واحداً بالضبط مع أن الاختبار لا يلمس إلا حقلاً واحداً من الحقول السبعة، فهو يعتمد أيضاً على أن العنوان المحفوظ في الحساب المشترك يملأ الحقول الستة الأخرى — وهي بيانات لم يُنشئها الاختبار. الأسطر 4–8 تسجّل الدخول عبر النموذج، والأسطر 9–12 تبني السلة بالنقر عبر صفحات المنتجات؛ وكلاهما يجعل الاختبار بطيئاً ويعرّضه لإخفاقات في شاشات لا يختبرها. سجّل الدخول عبر API وأنشئ السلة بـ POST /api/cart/items. ولا يُحذف شيء بعد الاختبار، فكل تشغيل يترك خلفه طلباً، والتشغيل الذي يتوقف قبل الدفع يترك في السلة أصنافاً تجعل إجمالي التشغيل التالي خاطئاً.
المراجعة، الجزء الثاني: المحددات والانتظار والتأكيدات. السطر 7: صنف CSS مولَّد (css-1x2y3z) قيمة مشتقة (hash) قد تتغير مع أي بناء للواجهة الأمامية — وهو أهشّ أنواع محددات CSS؛ استخدم getByRole("button", { name: "Sign in" }). السطران 10 و12: XPath مبني على مواضع العناصر (div[3]/div[2]) ينكسر بمجرد إضافة عنصر div غالف أو تغيّر التخطيط؛ استخدم الاسم الميسَّر لزر «Add to cart»، أو معرّف اختبار (test id) تتفق عليه مع عمر. السطران 16 و19: input.nth(3) يعتمد على ترتيب حقول الإدخال في الصفحة؛ استخدم getByLabel("City"). الأسطر 15 و17 و20 و21 تستخدم النص المرئي، وهو مما يراه المستخدم، لكن المحدد text= يطابق أي عنصر يحتوي نصه على الكلمة دون تمييز بين الأحرف الكبيرة والصغيرة: فـ text=Continue يطابق أيضاً رابط «Continue shopping» إن وُجد في الصفحة، فيفشل النقر حينها لأن عنصرين يطابقانه. أما getByRole("button", { name: "Continue", exact: true }) فدقيق. السطر 8: انتظار ثابت يخمّن مدة تسجيل الدخول؛ وعلى خادم أبطأ ينتقل السطر 9 إلى صفحة أخرى قبل أن تُنشأ الجلسة. انتظر شرطاً — الصفحة التي يصل إليها المستخدم — أو الأفضل: سجّل الدخول عبر API. الأسطر 10–13: ينتقل الاختبار مباشرةً بعد كل نقرة على «Add to cart» دون انتظار تحديث السلة، فقد يقطع الانتقالُ طلبَ الإضافة إلى السلة؛ انتظر عدّاد السلة أو استجابة ذلك الطلب. السطر 22: انتظار يخمّن مدة البوابة من 2 إلى 9 ثوانٍ. والتأكيد toHaveClass في السطر 23 يعيد المحاولة أصلاً (5 ثوانٍ افتراضياً)، فالانتظار لا يحمي من شيء ويضيف 8 ثوانٍ إلى كل تشغيل؛ استبدل البوابة باستجابة وهمية بدلاً من ذلك. السطران 14 و18: expect(await …textContent()) وcount() يقرآن مرة واحدة ويتسابقان مع الصفحة، ويعثران على العناصر بأصناف التنسيق (.total و.error)؛ استخدم await expect(locator).toHaveText(…) على معرّف اختبار مثل cart-total، وتحقّق من رسالة الخطأ التي تسمّي الحقل لا من عدد عناصر .error. السطر 23: صنف CSS تفصيل تنفيذي؛ تحقّق مما يراه المستخدم — عنوان التأكيد ورقم الطلب وعنوان URL. وتتفق إخفاقات CI الثلاثة التي نجحت محلياً مع هذه الصورة: خادم CI أبطأ من الحاسوب المحمول، فيظهر أثر الانتظار الثابت لثلاث ثوانٍ وطلبات السلة المقطوعة هناك أولاً؛ وخطّان يعملان في الوقت نفسه يتشاركان حساباً واحداً وسلة واحدة؛ ورفض البوابة نفسها (نحو 1 من كل 20) يُفشل الاختبار لسبب لا علاقة له بالإصدار 3.4. ودون آثار (traces) لا يستطيع أحد أن يعرف أيّ هذه الأسباب كان.
ما ينتمي إلى مستوى الواجهة. قواعد التحقق الـ 23 قواعد عمل كثيرة المدخلات، وهي ما يصفه الدليل بأنه غير مناسب لاختبار الواجهة. فحوص جهة المتصفح مكانها اختبارات الوحدة (Unit tests) للواجهة الأمامية (حالة لكل قاعدة، أجزاء من الثانية لكل منها)، وفحوص جهة الخادم مكانها اختبارات API لـ POST /api/addresses (القيمة غير الصالحة لكل قاعدة، ورمز الحالة، وجسم الخطأ)؛ ويُختبر الخادم وحده لأن أي عميل يستطيع تجاوز المتصفح. وتحتفظ الواجهة باختبار واحد يثبت الربط: حقل غير صالح يعرض رسالة تسمّي الحقل وزر «Continue» لا ينتقل — قاعدة واحدة تكفي، مثلاً مدينة فارغة. فتصبح اختبارات نادية الـ 23 اختباراً واحداً. رحلات الواجهة للإصدار 3.4: (1) عميل يسجّل الدخول ويرى صفحة الحساب — وهو الاختبار الوحيد الذي يستخدم نموذج تسجيل الدخول؛ (2) عميل ببطاقة محفوظة يكمل إتمام الشراء — Given عميل مسجَّل الدخول لديه بطاقة محفوظة وسلة من صنفين، When يؤكد العنوان ويدفع بالبطاقة المحفوظة، Then تعرض صفحة التأكيد رقم الطلب والإجمالي 1,249.00 ريال؛ (3) دفع مرفوض — Given الشروط نفسها، When ترفض البوابة، Then تعرض صفحة إتمام الشراء رسالة الرفض ويمكن استخدام زر «Pay now» مجدداً؛ (4) ربط التحقق من العنوان كما سبق. أربعة اختبارات، سلوك واحد لكل منها.
الطبقات. tests/checkout.spec.ts وtests/login.spec.ts — المواصفات: النيّة فقط؛ لا محددات ولا انتظار ولا عناوين URL. pages/login-page.ts وpages/cart-page.ts وpages/checkout-page.ts وpages/confirmation-page.ts — كائنات الصفحات: محددات وإجراءات (checkout() وcontinueWithAddress() وpayWithSavedCard()) تعيد كائن الصفحة التالية أو بيانات؛ لا تأكيدات. components/address-form.ts وcomponents/header.ts — كائنات مكوّنات للأجزاء التي تظهر في عدة صفحات. api/customers-client.ts وapi/cart-client.ts — عملاء رقيقون فوق /api/test-support/customers و/api/auth/session و/api/cart/items. fixtures/customer.ts — fixture (عبر test.extend) ينشئ عميلاً قابلاً للحذف ببطاقة محفوظة قبل الاختبار ويحذفه بعده؛ وfixtures/gateway.ts — الاستجابة الوهمية عبر page.route لنطاق sandbox.paygate.example باستجابة مقبولة وأخرى مرفوضة. playwright.config.ts — baseURL، والمشاريع (المتصفحات وعرض جوال)، والمهل، وtrace وscreenshot وvideo، والمراسِلات (reporters)؛ وenv/staging.env للبيئة، والأسرار من مخزن أسرار CI. playwright-report/ وjunit.xml — مولَّدة ولا تُحفظ في المستودع أبداً. أبقِ الأغلفة رقيقة: لا «BasePage» يعيد بناء Playwright.
الاختبار. test("customer pays with a saved card", async ({ page, customer, cart, gateway }) => { … }). يستدعي الـ fixture المسمّى customer الطلب POST /api/test-support/customers ويطلب بطاقة محفوظة؛ وتعيد الاستجابة معرّف (id) عميل جديد وبريده وكلمة مروره، فيكون لكل اختبار حسابه الخاص (وعندما يختار الاختبار البريد بنفسه يضيف إليه معرّف تشغيل أو UUID، كما في layla.h+run8412@example.com). ثم يسجّل الـ fixture الدخول بـ page.request.post("/api/auth/session", { data: { email, password } }) — فـ page.request يشارك الصفحة ملفات تعريف الارتباط نفسها، فلا يُملأ أي نموذج — ويحذف العميل في مرحلة التفكيك (teardown). ويضيف cart المنتجين عبر POST /api/cart/items. ويسجّل gateway الاعتراض page.route("**/sandbox.paygate.example/v1/charges", route => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(approved) }))، حيث approved نسخة من استجابة قبول حقيقية من بيئة sandbox. ويردّ route.fulfill على الطلب المعترَض برمز الحالة والجسم المحددين، فلا يصل الطلب إلى البوابة أصلاً (ويؤدي cy.intercept الدور نفسه في Cypress). المتن: const cartPage = new CartPage(page); ثم await cartPage.goto(); ثم const checkout = await cartPage.checkout(); ثم await checkout.continueWithAddress(testAddress); ثم const confirmation = await checkout.payWithSavedCard(); ثم await expect(confirmation.heading).toBeVisible(); وawait expect(confirmation.total).toHaveText("1,249.00 SAR"); وawait expect(confirmation.orderNumber).toHaveText(/^ORD-\d{4}-\d{6}$/); وawait expect(page).toHaveURL(/\/orders\/ORD-/); — وtestAddress عنوان خيالي من طبقة بيانات الاختبار. لا waitForTimeout: فـ click() ينتظر تلقائياً حتى يصبح العنصر قابلاً للتفاعل، وكل expect يعيد المحاولة حتى ينجح أو تنتهي المهلة. ويستخدم اختبار الدفع المرفوض الـ fixtures نفسها مع استجابة وهمية بالرفض (لعدم كفاية الرصيد مثلاً)، ويتحقق من رسالة الرفض ومن أن زر «Pay now» مفعّل مجدداً. وعندما تكتفي الاختبارات بقراءة البيانات، فمشروع الإعداد (setup project) هو الخيار الأرخص: مشروع في playwright.config.ts يعمل قبل المشاريع الأخرى، فيسجّل الدخول مرة واحدة ويحفظ ملفات تعريف الارتباط والتخزين في ملف storageState تحمّله الاختبارات بعد ذلك (والمقابل في Cypress هو cy.session). ويعمل اختبار واحد منفصل وأبطأ ليلياً مقابل بيئة sandbox الحقيقية ليثبت أن التكامل ما زال يعمل؛ وهو يقبل القبول أو الرفض ما دامت الصفحة تعرض النتيجة المطابقة، فلا يجعله الرفض العشوائي من بيئة sandbox متذبذباً.
التشغيل. تتبع المصفوفة التحليلات: يغطي Chromium نسبة 74%، وEdge مبني على المحرك نفسه، فمشروع Edge منفصل لا يضيف إلا القليل؛ ويقوم WebKit مقام Safari بنسبته 21%، بعرض جوال لأن معظمها iOS؛ وينال Firefox بنسبته 4% مجموعة الدخان (smoke). ونسخة WebKit في Playwright ليست Safari، فتعمل رحلة إتمام الشراء أيضاً على Safari الحقيقي — عبر safaridriver أو سحابة أجهزة — مرة لكل مرشّح إصدار. طلب الدمج: مجموعة الدخان (الرحلات الأربع) على Chromium، بضع دقائق، إلى جانب اختبارات الوحدة وAPI؛ والفشل يمنع الدمج. الفرع الرئيسي والتشغيل الليلي: انحدار الواجهة الكامل على Chromium وعلى WebKit بعرض الجوال، ومجموعة الدخان على Firefox، مجزّأةً (sharding) على عدة مهام CI مع دمج التقارير؛ ويعمل اختبار الدفع مقابل بيئة sandbox الحقيقية ليلياً. بعد النشر: رحلات قرائية فقط على الإنتاج — فتح الصفحة الرئيسية، والبحث، وفتح صفحة منتج — ولا طلب ولا دفع أبداً. الأدلة: trace: "retain-on-failure" وscreenshot: "only-on-failure" وvideo: "retain-on-failure"، وتقرير HTML وjunit.xml يُرفعان كمخرجات بناء (artifacts) حتى عندما تفشل المهمة، وإصدارات Playwright والمتصفحات مثبّتة، وبناء الإصدار 3.4 قيد الاختبار مسجَّل في التقرير.
الرد على خطة نادية. النقطة 1: 23 اختبار قاعدة تجعل الحزمة أبطأ بدقائق ولا تخبر الفريق شيئاً لا يخبره به اختبار وحدة أو API أسرع وأدق — اختبار ربط واحد في الواجهة، والقواعد الـ 23 تُختبر على مستوى الوحدة وAPI بدلاً من ذلك؛ واعرض العمل معها ثنائياً على اختبارات API. النقطة 2: أربعة متصفحات مع كل طلب دمج تضاعف المدة أربع مرات، وEdge لا يضيف إلا القليل فوق Chromium، وFirefox يضيف 4% يغطيها التشغيل الليلي أصلاً — الدخان على Chromium لكل طلب دمج، والمصفوفة ليلياً. النقطة 3: دون آثار أو لقطات شاشة لم يكن ممكناً إلا تخمين سبب تشغيلات CI الثلاثة الفاشلة، ولهذا أُعيد تشغيلها محلياً فنجحت وبقي سببها مجهولاً — احتفظ بالأدلة عند الفشل فقط، فكلفة ذلك قليلة في التشغيل الناجح. ويمرّ الاختبار بمراجعة الشيفرة كشيفرة الإنتاج: نتائج الخطوتين 1 و2 هي تعليقات المراجعة، وتُحدَّث كائنات الصفحات في طلب الدمج نفسه الذي يغيّر الواجهة، ويتفق عمر على معرّفات اختبار للعناصر التي لا اسم ميسَّراً لها.
إن اختلفت إجابتك فراجعها مقابل قواعد الدليل: اختبار واجهة فقط حيث يكون الخطر في الرحلة نفسها؛ وحالة تُنشأ عبر API، فريدة لكل اختبار وتُحذف بعده؛ ومحددات مبنية على ما يراه المستخدم؛ وانتظار لشرط لا لوقت أبداً؛ وتأكيدات على ما يراه المستخدم تعيد المحاولة؛ وإعدادات وأسرار خارج الاختبارات. والتقسيم المختلف مع سبب مقبول — مثل اختبار رسالة الرفض في اختبار مكوّن (component test) للواجهة الأمامية بدلاً من اختبار واجهة، لأن المتصفح يتصل بالبوابة مباشرةً ومعالجة الرفض منطق في الواجهة الأمامية وحدها — أما الانتظار الثابت أو الحساب المشترك أو عنوان URL المكتوب داخل الاختبار فغير مقبول.
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.
جزء من مسار تعلم ضمان الجودة
قبل هذا الموضوع
الموضوعات التالية
موضوعات أخرى في هذه الوحدة
الأدوات المذكورة في هذا الدليل
أنشئ طلبات HTTP وأرسلها (الطريقة وعنوان URL والترويسات والمصادقة والجسم) وافحص الاستجابة — بديل خفيف لـ Postman داخل متصفحك.
ولّد سجلات مستخدمين وهمية (الاسم واسم المستخدم والبريد الإلكتروني وكلمة المرور وبذرة الصورة الرمزية والدور) لحسابات الاختبار.
مصنع متكامل لبيانات الاختبار الاصطناعية: مستخدمون وعملاء وشركات وعناوين وأرقام وتواريخ وسلاسل نصية بصيغة JSON أو CSV أو SQL أو XML.
ولّد معايير قبول بصيغة Given/When/Then وقائمة تحقق لتعريف الإنجاز (Definition of Done) من قصة مستخدم.
ولّد حالات اختبار منظّمة (المعرّف والعنوان والشروط المسبقة والخطوات والنتيجة المتوقّعة والأولوية) من وصف الميزة وقائمة الحقول.
اختبر التعابير النمطية مباشرةً مع إبراز التطابقات ومجموعات الالتقاط ومعاينة الاستبدال.
ولّد استجابات API وهمية كاملة (الحالة والترويسات وجسم JSON) لحالات النجاح والقوائم والتفاصيل والأخطاء والنتائج الفارغة.