→ العودة إلى المدونة
Design & UI/UX

اختبار إمكانية الوصول في Playwright باستخدام axe: حالات الواجهة

اختبر مربعات الحوار والتحقق من المدخلات والتنقل باستخدام Playwright وaxe، مع أمثلة قابلة للتشغيل وفحوص للتركيز وإرشادات CI وحدود التغطية.

بقلم Hamza Diaz
5 أكتوبر 202612 دقيقة قراءة93 مشاهدة

يكون اختبار إمكانية الوصول في Playwright أكثر فائدة عندما يصل الاختبار إلى حالة الواجهة التي يحتاجها المستخدم: مربع حوار مفتوح، أو نموذج رُفض إرساله، أو قائمة تنقل موسّعة. لا يستطيع فحص الصفحة في حالتها الأولية أن يفحص عناصر تحكم لم تُعرض بعد. يتولى Playwright تنفيذ التفاعل، ثم يفحص axe-core نموذج DOM الناتج وفق قواعد محددة.1

يقدّم هذا الدليل صفحة اختبار محلية قابلة للتشغيل مع ثلاثة اختبارات، ثم يوضح ما ينبغي تعديله عند تطبيقها على تطبيقك. هذه طريقة لاختبارات الانحدار، وليست تقييمًا للمطابقة مع WCAG. ولا يزال التقييم اليدوي لإمكانية الوصول واختبارات المشاركة مع مستخدمين ذوي احتياجات متنوعة ضروريين.1

اختر الحالات، لا المسارات فقط

ابدأ بمهمة واحدة مهمة، مثل تغيير عنوان البريد الإلكتروني. دوّن الحساب والبيانات التي تبدأ بها، والإجراء، والنتيجة المتوقعة، ونطاق الفحص. أدرج سيناريوهات الخطأ والتعافي منه، لا الإرسال الناجح وحده. فقد يحتوي المسار (route) الواحد على عدة حالات يمكن اختبار كلٍّ منها على حدة.

استخدم STATE قائمةَ تحقق للتخطيط: اختيار الحالة، ثم إطلاقها، ثم التحقق من النتيجة، ثم فرز المشكلات المكتشفة، ثم توثيق الأدلة. هذا اختصار تحريري يساعد على التذكّر، وليس معيارًا. والمخطط التالي تقابله هذه الخطوات الخمس في صورة نصية.

flowchart TD A[اختيار حالة] --> B[إطلاق التفاعل] B --> C[التحقق والفحص] C --> D[فرز المشكلات المكتشفة] D --> E[حفظ الأدلة]

قائمة صغيرة وصريحة بالحالات أفضل من الاعتماد على عدد المسارات. في إعدادات الحساب، اختبر فتح مربع الحوار وإغلاقه، والإدخال غير الصالح، والتعافي الناجح منه. وفي التنقل، اختبر الحالتين الموسّعة والمطويّة في إطار عرض ضيق. وأضف المصادقة والأذونات ومفاتيح تفعيل الميزات وحالات فشل التحميل حيثما تغيّر عناصر التحكم التي تظهر للمستخدم.

انتظر نتيجة الإجراء

لا يطبّق Playwright فحوص الانتظار نفسها على كل الإجراءات. يتحقق click() من أن العنصر مرئي ومستقر ويستقبل الأحداث ومُفعَّل. أما fill() فيتحقق من أن العنصر مرئي ومُفعَّل وقابل للتحرير، ولا يشترط أن يكون مستقرًا أو أن يستقبل الأحداث. واكتمال النقر أو التعبئة لا يعني أن التحقق من المدخلات أو العرض غير المتزامن قد انتهى.2

قبل استدعاء analyze()، استخدم عبارة تحقق تعيد المحاولة تلقائيًا وتنتظر النتيجة الفعلية: ظهور مربع حوار له اسم، أو ظهور رسالة خطأ، أو احتواء رسالة الحالة على النص المتوقع. وتجنّب الانتظار لمدد عشوائية. إذا كان تطبيقك يحمّل الخيارات بعد فتح مربع الحوار، فلا يكفي التحقق من ظهور إطاره فقط؛ بل انتظر الخيارات أيضًا أو حالة جاهزية موثّقة.12

شغّل المثال محليًا

يستخدم المثال @playwright/test 1.58.1 و@axe-core/playwright 4.11.1. احفظ الملفات التالية في مجلد فارغ على جهاز يتوفر فيه Node.js وnpm. يعتمد ملف الإعداد على متصفح Google Chrome المثبّت، ولا يُنزّل أي متصفح. وإذا أردت استخدام Chromium المضمّن بدلًا منه، فاحذف channel ونفّذ npx playwright install chromium.

npm init -y
npm install --save-dev --save-exact @playwright/test@1.58.1 @axe-core/playwright@4.11.1
npx playwright test

نفّذ الأمر الأخير بعد حفظ الملفات الثلاثة. احتفظ بملف القفل الذي يُنشأ في نظام التحكم بالإصدارات، لأن إصدار الحزمة المغلِّفة وحده لا يثبّت كل الاعتماديات غير المباشرة. في 5 أكتوبر 2026، نجحت الاختبارات الثلاثة في Chrome المحلي مع axe-core 4.11.4، وهو الإصدار الذي ثُبّت ضمن اعتماديات حزمة التكامل. كان ذلك تشغيلًا على صفحة اختبار اصطناعية، وليس اختبارًا لخدمة حسابات في بيئة الإنتاج.

1. احفظ fixture.html

تستخدم صفحة الاختبار مربع حوار أصليًا يمنع التفاعل مع الخلفية، وتحققًا من البريد الإلكتروني في جانب العميل، وتنقلًا عاديًا بنمط الإظهار والإخفاء. زر الحفظ فيها يغيّر نصًا محليًا فقط، فلا يرسل أي بريد ولا يخزّن أي شيء. وحلقة التركيز لا تتعامل إلا مع حقول الإدخال والأزرار في هذه الصفحة؛ أما التنفيذ القابل لإعادة الاستخدام فيجب أن يراعي عناصر التحكم الأخرى والعناصر المخفية أو المعطّلة. هذه بيانات اختبار، وليست مكتبة مكوّنات جاهزة للإنتاج.

<!doctype html><html lang="en"><title>Account settings fixture</title>
<style>body { color:#111; background:#fff; font:18px sans-serif } :focus-visible {outline:3px solid #005fcc} dialog {color:#111;background:#fff} button, input, a {font:inherit; min-height:32px; margin:4px} a {display:inline-block; padding:4px}</style>
<main><h1>Account settings</h1>
<button id="open">Edit email</button>
<dialog aria-labelledby="heading" aria-modal="true">
<h2 id="heading">Edit email</h2><form novalidate>
<label for="email">Email</label><input id="email" type="email" autocomplete="email" autofocus>
<p id="error" hidden>Enter a valid email address.</p>
<button type="submit">Save</button><button type="button" id="close">Cancel</button>
</form></dialog>
<button id="nav" aria-expanded="false" aria-controls="links">Navigation</button>
<nav id="links" aria-label="Account" hidden><a href="#profile">Profile</a><a href="#help">Help</a></nav>
<p role="status" id="status"></p></main>
<script>
const dialog = document.querySelector('dialog');
const opener = document.querySelector('#open');
const email = document.querySelector('#email');
const error = document.querySelector('#error');
const nav = document.querySelector('#nav');
const links = document.querySelector('#links');
opener.onclick = () => dialog.showModal();
document.querySelector('#close').onclick = () => dialog.close();
dialog.addEventListener('close', () => opener.focus());
dialog.addEventListener('keydown', event => {
 const controls = [...dialog.querySelectorAll('input, button')];
 const first = controls[0], last = controls.at(-1);
 if (event.key === 'Tab' && event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
 if (event.key === 'Tab' && !event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
});
document.querySelector('form').onsubmit = event => {
 event.preventDefault();
 const invalid = !email.value || !email.validity.valid;
 error.hidden = !invalid;
 if (invalid) { email.setAttribute('aria-invalid','true'); email.setAttribute('aria-describedby','error'); email.focus(); }
 else { email.removeAttribute('aria-invalid'); email.removeAttribute('aria-describedby'); dialog.close(); document.querySelector('#status').textContent = 'Email saved.'; }
};
nav.onclick = () => { const open = nav.getAttribute('aria-expanded') === 'true'; nav.setAttribute('aria-expanded', String(!open)); links.hidden = open; };
</script></html>

2. احفظ playwright.config.mjs

import { defineConfig } from '@playwright/test';
export default defineConfig({ testMatch: /states.spec.mjs/, workers: 1, retries: 0, use: { channel: 'chrome' }, reporter: [['list'], ['json', { outputFile: 'test-results.json' }]] });

3. احفظ states.spec.mjs

تعبّر محددات المواقع المبنية على الدور والتسمية عن الأسماء القابلة للوصول المتوقعة. وهي تحدد سلوكًا متوقعًا مفيدًا، لكن العثور على عنصر تحكم من خلال دوره لا يثبت أن التفاعل معه قابل للوصول بالكامل.6

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import { readFileSync } from 'node:fs';

test.beforeEach(async ({ page }) => {
  await page.setContent(readFileSync('fixture.html', 'utf8'));
});

async function scan(page, testInfo, state) {
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
    .analyze();
  await testInfo.attach(`axe-${state}`, {
    body: JSON.stringify(results, null, 2),
    contentType: 'application/json',
  });
  expect(results.violations).toEqual([]);
  // This small fixture has a strict review gate; real apps need triage.
  expect(results.incomplete).toEqual([]);
}

test('dialog opens, contains focus and returns it', async ({ page }, testInfo) => {
  const opener = page.getByRole('button', { name: 'Edit email', exact: true });
  await opener.focus();
  await page.keyboard.press('Enter');
  const dialog = page.getByRole('dialog', { name: 'Edit email', exact: true });
  await expect(dialog).toBeVisible();
  await expect(dialog).toHaveAttribute('aria-modal', 'true');
  const email = dialog.getByLabel('Email', { exact: true });
  const save = dialog.getByRole('button', { name: 'Save', exact: true });
  const cancel = dialog.getByRole('button', { name: 'Cancel', exact: true });
  await expect(email).toBeFocused();
  await page.keyboard.press('Tab');
  await expect(save).toBeFocused();
  await page.keyboard.press('Tab');
  await expect(cancel).toBeFocused();
  await page.keyboard.press('Tab');
  await expect(email).toBeFocused();
  await page.keyboard.press('Shift+Tab');
  await expect(cancel).toBeFocused();
  await scan(page, testInfo, 'dialog');
  await page.keyboard.press('Escape');
  await expect(dialog).toBeHidden();
  await expect(opener).toBeFocused();
  await page.keyboard.press('Enter');
  await expect(email).toBeFocused();
  await page.keyboard.press('Shift+Tab');
  await page.keyboard.press('Enter');
  await expect(dialog).toBeHidden();
  await expect(opener).toBeFocused();
});

test('invalid email is associated and can be corrected', async ({ page }, testInfo) => {
  await page.getByRole('button', { name: 'Edit email', exact: true }).click();
  const dialog = page.getByRole('dialog', { name: 'Edit email', exact: true });
  await expect(dialog).toBeVisible();
  const email = dialog.getByLabel('Email', { exact: true });
  await email.fill('wrong');
  await dialog.getByRole('button', { name: 'Save', exact: true }).click();
  await expect(page.locator('#error')).toBeVisible();
  await expect(email).toHaveAttribute('aria-invalid', 'true');
  await expect(email).toHaveAccessibleDescription('Enter a valid email address.');
  await expect(email).toBeFocused();
  await scan(page, testInfo, 'invalid');
  await email.fill('reader@example.com');
  await dialog.getByRole('button', { name: 'Save', exact: true }).click();
  await expect(dialog).toBeHidden();
  await expect(page.getByRole('status')).toHaveText('Email saved.');
  await expect(page.locator('#error')).toBeHidden();
  await expect(page.locator('#email')).not.toHaveAttribute('aria-invalid', 'true');
  await scan(page, testInfo, 'saved');
});

test('navigation disclosure exposes ordinary links', async ({ page }, testInfo) => {
  await page.setViewportSize({ width: 375, height: 667 });
  const trigger = page.getByRole('button', { name: 'Navigation', exact: true });
  const links = page.getByRole('navigation', { name: 'Account', exact: true });
  await expect(trigger).toHaveAttribute('aria-expanded', 'false');
  await expect(links).toBeHidden();
  await trigger.focus();
  await page.keyboard.press('Space');
  await expect(trigger).toHaveAttribute('aria-expanded', 'true');
  await expect(links).toBeVisible();
  await page.keyboard.press('Tab');
  await expect(links.getByRole('link', { name: 'Profile', exact: true })).toBeFocused();
  await scan(page, testInfo, 'navigation');
  await page.keyboard.press('Shift+Tab');
  await page.keyboard.press('Enter');
  await expect(trigger).toHaveAttribute('aria-expanded', 'false');
  await expect(links).toBeHidden();
  await expect(trigger).toBeFocused();
  await expect(page.getByRole('link', { name: 'Profile', exact: true })).toHaveCount(0);
  await scan(page, testInfo, 'navigation-collapsed');
});

ما الذي تثبته عبارات التحقق

يفتح الاختبار الأول مربع الحوار من لوحة المفاتيح، وينتظر ظهوره وظهور اسمه القابل للوصول، ويتحقق من موضع التركيز عند فتحه، ثم يتحقق من طرفي تسلسل التنقل بمفتاح Tab. ويُغلق مفتاح Escape وزر الإلغاء المرئي مربع الحوار، ويعيدان التركيز إلى العنصر الذي فتحه. يصف نمط مربع الحوار في APG هذه السلوكيات، لكن التركيز الأولي لا يلزم أن يكون دائمًا على أول حقل إدخال: فقد يتطلب المحتوى الطويل وضع التركيز على عنوان، وقد يتطلب تأكيد إجراء لا يمكن التراجع عنه وضع التركيز على الخيار الأكثر أمانًا. ويمكن كذلك نقل التركيز عند الإغلاق إلى خطوة تالية منطقية إذا اختفى العنصر الذي فتح الحوار أو اقتضى سير العمل ذلك.3

يمنع showModal() الأصلي في صفحة الاختبار التفاعل مع محتوى الخلفية. أما مربع الحوار المخصّص فيحتاج إلى فحص منفصل يتأكد من تعذّر استخدام عناصر التحكم في الخلفية؛ لأن ضبط aria-modal="true" وحده لا يحقق هذا السلوك.3 افحص أيضًا مؤشر التركيز المرئي وترتيب القراءة، فالتحقق البرمجي من التركيز لا يكشف ما إذا كانت ترويسة ثابتة تحجب مؤشر التركيز.

ينتظر اختبار التحقق من المدخلات ظهور رسالة الخطأ، وaria-invalid، والوصف القابل للوصول للحقل، وانتقال التركيز. ثم يُدخل قيمة صالحة ويتحقق من إغلاق مربع الحوار، وزوال حالة الخطأ، وظهور رسالة النجاح. والاكتفاء بفحص aria-describedby سيكون أضعف، لأن العنصر الذي يشير إليه قد يكون مفقودًا أو قد يحتوي على نص خاطئ. وتستخدم عبارة التحقق الأخيرة من السمة محدد DOM، لأن مربع الحوار بعد إغلاقه لم يعد يظهر لمحدد الدور الافتراضي.

يثبت التحقق من نص منطقة الحالة أن DOM قد تحدّث، لكنه لا يثبت ما أعلنه قارئ الشاشة ولا توقيت إعلانه. اختبر الإعلانات على تركيبات المتصفحات والتقنيات المساعدة التي تدعمها. وفي التطبيق الحقيقي، انتظر أيضًا الاستجابة الفعلية لحفظ البيانات وتحقق من القيمة المحفوظة؛ أما صفحة الاختبار هذه فلا تفعل أيًّا من ذلك عن قصد.

التنقل بنمط الإظهار والإخفاء ليس قائمة ARIA

يستخدم الاختبار الثالث زرًا يُظهر روابط عادية. ويتحقق من aria-expanded، ومن ظهور الروابط، ومن التفعيل بمفتاح المسافة، ومن الانتقال إلى الرابط الأول بمفتاح Tab، ومن طي القائمة بمفتاح Enter. ولا يحتاج التنقل المعتاد في المواقع إلى دور menu في ARIA ولا إلى نموذج لوحة المفاتيح الخاص به.7

أما إذا كانت لديك قائمة أوامر فعلية، فاتبع متطلبات نمط زر القائمة: زر يحمل aria-haspopup="menu"، وقيمة aria-expanded متزامنة مع حالة القائمة، وقائمة لها اسم، وعناصر قائمة لها أسماء صحيحة. يجب أن يفتح مفتاحا Enter والمسافة القائمة وينقلا التركيز إلى عنصرها الأول.4 أضف اختبارات للتنقل بمفاتيح الأسهم الذي تدعمه القائمة، وللتفعيل، وللإغلاق بمفتاح Escape مع إعادة التركيز. ولا تنقل عبارات التحقق الخاصة بنمط الإظهار والإخفاء إلى عنصر قائمة ثم تعتبر التغطية مكتملة.

فسّر نتيجة axe، بما فيها النتائج غير المكتملة

تختار الدالة المساعدة وسوم WCAG للمستويين A وAA حتى الإصدار 2.2، وفق ما يتوفر في هذا الإصدار من axe. وهي بذلك تختار القواعد الآلية التي تحمل هذه الوسوم، لا كل معايير النجاح في هذين المستويين. تتضمن فحوص axe الافتراضية أيضًا قواعد مصنّفة ضمن أفضل الممارسات، والتصفية حسب وسوم WCAG تغيّر هذا النطاق.15

تحتوي النتيجة على violations وpasses وincomplete وinapplicable. النتيجة غير المكتملة تحتاج إلى مراجعة، لأن axe لم يتمكن من تحديد ما إذا كانت العقدة المتأثرة قد نجحت أم أخفقت. أما القاعدة غير القابلة للتطبيق فلم تجد محتوى ذا صلة، وهذا لا يدل على أنك اختبرت تلك الميزة.5

في هذا المثال الصغير، يفشل الاختبار عند وجود انتهاكات أو نتائج غير مكتملة، بعد إرفاق النتيجة الكاملة. أما في التطبيق الحقيقي، فقد تختار بدلًا من ذلك إحالة النتائج غير المكتملة إلى قائمة مراجعة لها مسؤول محدد. واجعل ذلك سياسة صريحة لها موعد نهائي للمراجعة، بدلًا من تجاهل المصفوفة بصمت أو اعتبارها نجاحًا. احفظ مع المخرجات إصدار المحرك من results.testEngine، والمتصفح أو المشروع، واسم الحالة، ومعرّف الإيداع. وأعد ضبط خط الأساس عن قصد بعد ترقية الاعتماديات.

حدّد نطاق الفحص بدقة

تفحص هذه الاختبارات الصفحة الحالية كاملة. يمكن أن يقلل .include('#panel') من الضوضاء عند فحص مكوّن بعينه، لكنه لا يثبت التغطية خارج تلك الشجرة الفرعية. ويستبعد .exclude() العنصر المحدد وكل العناصر المتفرعة منه من جميع القواعد، لذا قد يُخفي الاستبعاد الواسع حالات انحدار لا علاقة لها به.1

تتولى حزمة التكامل مع Playwright حقن محرك axe في الإطارات تلقائيًا. ويعطّل وضعها القديم اختبار الإطارات التي تنتمي إلى أصل مختلف، ويُبلغ عن الإطارات التي لم تُختبر. وأي خطأ في الفحص أو إطار لم يُختبر يمثل فجوة في التغطية، لا نتيجة سليمة.8 انتظر تحميل المحتوى المضمّن، وراجع النتائج المتعلقة بالإطارات، واختبر سير العمل المهم داخل المحتوى المضمّن بشكل صريح.

تدعم محددات المواقع في Playwright جذور الظل المفتوحة عمومًا، باستثناء XPath، ولا تدعم جذور الظل المغلقة. يستطيع axe اجتياز Shadow DOM المفتوح، لكن نجاح محدد الموقع لا يثبت في حد ذاته أن الفحص غطّى المحتوى.56 وثّق الجذور المغلقة والعناصر التابعة لجهات خارجية كلًّا على حدة. لا تحتوي صفحة الاختبار أعلاه على أي iframe أو جذر ظل، لذا فإن نجاحها لا يثبت شيئًا عن أيٍّ منهما.

اجعل حالات الفشل في CI مفيدة

استخدم مستخدمين اصطناعيين في صفحات الاختبار، وخصّص حسابات للاختبارات التي تتطلب مصادقة، وقيّد الوصول إلى المخرجات المحفوظة. تحتوي نتائج axe على مقتطفات من DOM، وقد تكشف ملفات التتبع ولقطات الشاشة والسجلات بيانات شخصية وتفاصيل الجلسات. حدّد مدد الاحتفاظ، ونقِّ البيانات قبل مشاركتها.

شغّل اختبارات الحالات الحرجة على طلبات السحب. وأضف إطارات عرض تمثيلية للأجهزة المحمولة، والمتصفحات الأخرى المدعومة، والفحوص اليدوية وفق جدول يناسب المنتج. ولا تقدّم أبدًا نتيجة صفحة اختبار محدودة على Chrome على أنها تغطية لعدة متصفحات. وعند حدوث فشل، احتفظ بالحالة، ومعرّف القاعدة، والعناصر المتأثرة، وخطوات إعادة الإنتاج، والمسؤول المكلّف بالمعالجة. استثناء محدد النطاق وله تاريخ انتهاء أفضل من تعطيل قاعدة في التطبيق كله.

في مسارات الحساب، وسّع هذا النهج ليشمل الشاشات البديلة وشاشات الاستعادة، كما هو موضح في دليل طرح مفاتيح المرور. ابدأ بمهمة مستخدم تفشل، واكتب سلوكها المتوقع الذي يمكن ملاحظته، ثم أصلح المكوّن وأعد اختبار تلك الحالة. هكذا تحصل على اختبار انحدار يستطيع غيرك صيانته.

النقاط الرئيسية

  • 1انتقل إلى كل حالة معروضة وتحقّق منها قبل تشغيل axe؛ فاكتمال الإجراء لا يعني أن الواجهة جاهزة.
  • 2تحقّق من أسماء مربعات الحوار، وبقاء التركيز داخلها، وإغلاقها، والموضع المناسب لعودة التركيز.
  • 3تحقّق من رسائل الخطأ المرئية وارتباطها بالحقول ومن التعافي الناجح معًا.
  • 4تعامل مع النتائج غير المكتملة على أنها تحتاج إلى مراجعة، وسجّل الإصدارات ونطاقات الفحص.
  • 5استخدم STATE لاختيار الحالات وإطلاقها، والتحقق من النتائج، وفرز المشكلات المكتشفة، وتوثيق الأدلة.

الخلاصة

اختر حالة بالغة الأهمية للمستخدم، وحدّد قبل الفحص ما يجب أن يظهر فيها وما يجب أن تقدمه من دلالات وسلوك عند استخدام لوحة المفاتيح. واحفظ الاختبار ونطاق الفحص وأدلة المراجعة معًا، حتى يمكن إعادة إنتاج أي فشل في المستقبل.

الأسئلة الشائعة

هل يثبت نجاح فحص axe المطابقة مع WCAG؟

لا. فهو يعرض نتائج فحوص آلية محددة للحالة المعروضة. ولا يزال التقييم اليدوي، والاختبار باستخدام التقنيات المساعدة، واختبارات المشاركة مع مستخدمين ذوي احتياجات متنوعة ضرورية.

هل أفحص الصفحة كاملة أم مكوّنًا واحدًا؟

افحص الصفحة كاملة في تلك الحالة متى أمكن. الفحص المحدود النطاق مفيد لفحص مكوّن بعينه، لكن سجّل حدوده واحتفظ بفحوص منفصلة للمحتوى المستبعد.

ماذا أفعل بالنتائج غير المكتملة؟

راجع العقد المتأثرة، لأن axe لم يتمكن من تحديد النجاح أو الإخفاق. فإما أن تجعل الاختبار يفشل إلى حين المراجعة، أو تتابع هذه النتائج في قائمة لها مسؤول محدد ومهلة زمنية.

هل ينتظر fill حتى يتوقف العنصر عن الحركة؟

لا. يتحقق Playwright في fill من أن العنصر مرئي ومُفعَّل وقابل للتحرير. لذا تحقّق من الحالة التي ينتقل إليها التطبيق بشكل منفصل قبل الفحص.

هل يمكنني إعادة استخدام هذه الاختبارات في بيئة الإنتاج؟

استبدل صفحة الاختبار الاصطناعية بتطبيقك، وحدّث الأسماء القابلة للوصول والبيانات وعبارات التحقق من الجاهزية. وأضف المتصفحات المدعومة وإطارات العرض والفحوص اليدوية؛ فالمثال لا يختبر حفظ البيانات ولا خدمة حقيقية.

المصادر

شارك هذا المقال

Hamza Diaz

بقلم

Hamza Diaz

حمزة دياز هو مؤسس Optijara، حيث يبني وكلاء ذكاء اصطناعي عمليين، وأنظمة أتمتة، وسير عمل Copilot للشركات الخدمية. يكتب عن تشغيل الذكاء الاصطناعي، واستراتيجية الوكلاء، والتطبيق الواقعي للفرق التي تريد أنظمة مفيدة بدلًا من الضجيج.

مقالات ذات صلة