أساسيات البرمجة لأتمتة الاختبار
الاختبارات الآلية برامج صغيرة. فملف الاختبار يستورد مشغّل الاختبارات (Test Runner) وكائن صفحة، ويمرّ في حلقة على بيانات الاختبار، وينشئ كائناً، وينتظر إجراءات المتصفح، ويتحقق من نتيجة. غطّى درس «أساسيات JavaScript للمختبِرين» القيم والدوال والوعود (Promises) وقراءة تتبّع المكدس؛ ويضيف هذا الدرس الميزات اللغوية الخمس التي يُبنى منها كل ملف اختبار تقريباً: تدفق التحكم، والوحدات البرمجية (Modules)، والأصناف (Classes)، والشيفرة غير المتزامنة مع معالجة الأخطاء، والأنواع الأساسية.
الأمثلة بلغة TypeScript، وهي JavaScript مضافاً إليها توصيفات أنواع مثل email: string يفحصها المترجم tsc ومحرّر الشيفرة، ثم تُحذف قبل تشغيل الشيفرة. ويشغّل Playwright ملفات الاختبار المكتوبة بـTypeScript دون إعداد إضافي. والهدف أن تفتح أي ملف اختبار في فريقك، وتقول ما يفعله كل سطر، وتكتب اختباراً جديداً بالشكل نفسه.
لماذا يهم
المختبِر الذي لا يقرأ شيفرة الاختبار لا يستطيع أن يحكم هل كشف الاختبار الفاشل عيباً في المنتج أم أن فيه عيباً هو نفسه، ولا يستطيع مراجعة تغيير زميله. والأسوأ أن بعض عيوب الاختبار تجعله ينجح بينما المنتج معطّل: نسيان await يجعل التأكيد يعمل قبل أن تتغير الصفحة، وtry/catch الذي يسجّل الخطأ ويتابع يخفي الفحص الفاشل عن المشغّل. وحزمة مليئة بهذه الاختبارات تعرض اللون الأخضر ولا تكشف شيئاً.
كما أن شيفرة الاختبار تعمل مع كل تغيير، فبنيتها تحدد كلفة أي تعديل صغير في واجهة المستخدم: تعديل واحد في ملف واحد، أو التعديل نفسه في كل اختبار.
تدفق التحكم: if وfor...of والدوال السهمية
تستخدم شيفرة الاختبار القرارات والحلقات نفسها التي يستخدمها أي برنامج، لكن باعتدال. فـif/else يختار مساراً، مثلاً لإغلاق شريط ملفات تعريف الارتباط فقط عندما يظهر. وfor...of ينفّذ الخطوات نفسها مرة لكل عنصر في مصفوفة، وفي شيفرة الاختبار تحمل المصفوفة كائنات بيانات اختبار: كائن لكل حالة، فيه المدخلات والنتيجة المتوقعة. وتنشئ الحلقة اختباراً مسمّى لكل كائن، فإذا فشل أحدها عرف التقرير أي حالة فشلت.
الدالة السهمية (Arrow Function) مثل (a, b) => a + b دالة مكتوبة كقيمة. وتأخذ المشغّلات الدوال معاملاتٍ، لذلك يُكتب جسم كل اختبار دالةً تُمرَّر كدالة رد نداء (Callback)، وهي في الغالب دالة سهمية: test('name', async () => { ... }). يخزّن المشغّل هذه الدالة ويستدعيها لاحقاً، ولهذا لا يعمل جسمها أثناء تحميل الملف. وفي async ({ page }) => { ... } أدناه يمرّر المشغّل إلى الدالة كائناً، فيستخرج { page } خاصيته page (ويسمّى هذا التفكيك Destructuring)، وهي صفحة المتصفح الخاصة بهذا الاختبار.
- اكتب الرسالة المتوقعة في كائن البيانات، ولا تحسبها بـ
ifداخل الاختبار، وإلا فقد يتفق الاختبار مع العيب. for...ofيمر على القيم، وfor...inيمر على مفاتيح الكائن؛ وفي شيفرة الاختبار تريدfor...ofفي الغالب.
const cases = [
{ email: 'sara@example.com', password: 'Correct-1', message: 'Welcome, Sara' },
{ email: 'sara@example.com', password: 'wrong', message: 'Invalid login' },
];
for (const c of cases) {
test('login: ' + c.message, async ({ page }) => { // one test per data object
// the steps use c.email, c.password and c.message
});
}الوحدات البرمجية: export وimport
الوحدة البرمجية (Module) ملف يصرّح بما يشاركه. فـexport class LoginPage يجعل الاسم متاحاً، وimport { LoginPage } from './pages/login-page' يجلبه إلى ملف آخر. ولا يُشارَك شيء بالمصادفة: الاسم غير المصدَّر يبقى خاصاً بملفه، والملف الذي يحتاج شيئاً يجب أن يستورده.
لهذا يبدأ ملف الاختبار بنوعين من الاستيراد: test وexpect من حزمة المشغّل، وهما الدالتان اللتان تسجّلان اختباراً وتفحصان نتيجة، ثم كائنات الصفحات والبيانات من ملفاتك أنت. فيُقرأ الملف كسيناريو، وتبقى المحددات (Locators)، أي التعابير التي تجد عنصراً في الصفحة مثل getByLabel('Email')، في مكان واحد: عندما يتغير نموذج تسجيل الدخول تعدّل كائن الصفحة، فيُصلَح كل ملف اختبار يستورده.
- المسارات النسبية تبدأ بـ
./أو../؛ أما أسماء الحزم مثل@playwright/testفلا. import type { Page }يستورد نوعاً فقط، ويحذفه المترجم (Compiler) من المخرجات.
// pages/login-page.ts
export class LoginPage { /* ... */ }
// login.spec.ts
import { test, expect } from '@playwright/test'; // the runner
import { LoginPage } from './pages/login-page'; // your page objectالأصناف والنسخ: شكل كائن الصفحة
الصنف (Class) قالب لكائنات تشترك في الحقول (البيانات) والدوال (Methods). وnew LoginPage(page) ينشئ نسخة (Instance): يشغّل المُنشئ (Constructor) الذي يخزّن صفحة المتصفح في حقل، ويعيد كائناً تصل دواله إلى ذلك الحقل عبر this. وكل اختبار ينشئ نسخته الخاصة، فلا يتشارك اختباران كائن صفحة واحداً.
وكائن الصفحة (Page Object) هو هذا بالضبط: حقل واحد للصفحة، ومُنشئ يستقبلها، ودالة لكل شيء يستطيع المستخدم فعله في الشاشة، مثل open() وsignIn(email, password). تخفي الدوال المحددات والنقرات، ويستدعيها الاختبار بأسمائها. ويضيف توثيق Selenium قاعدة تصحّ في كل لغة: لا تحتوي كائنات الصفحات على تأكيدات، لأن الاختبار هو من يقرر ما الصحيح؛ والفحص الوحيد الذي يسمح به داخل كائن الصفحة هو التحقق من أن الصفحة حُمّلت بشكل صحيح.
private readonly pageتعني أن الشيفرة داخل الصنف وحدها تستخدم الحقل، وأن الحقل لا يُعاد تعيينه بعد المُنشئ؛ والاختبار الذي يحاول الوصول إلىlogin.pageيحصل على خطأ من المترجم.
import type { Page } from '@playwright/test';
export class LoginPage {
private readonly page: Page; // a field
constructor(page: Page) { // runs once, on new
this.page = page; // this = the instance being built
}
async open(): Promise<void> { // a method
await this.page.goto('/login'); // relative to baseURL in the config
}
async signIn(email: string, password: string): Promise<void> {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Sign in' }).click();
}
}الشيفرة غير المتزامنة والأخطاء: انتظر كل شيء ولا تبتلع شيئاً
كل إجراء في المتصفح (goto وfill وclick) وكل تأكيد يكرّر المحاولة يُرجع وعداً (Promise)، لأن العمل يحدث في المتصفح ويستغرق وقتاً. والدالة التي تنتظر تُعلَن async، وداخلها يوقف await التنفيذ حتى يُحسم الوعد، ثم يتابع بالقيمة أو يرمي الخطأ. والقاعدة: ضع await قبل كل إجراء وكل تأكيد يعيد وعداً.
خطأ نسيان await من أول ما تبحث عنه عندما يتذبذب اختبار مكتوب بـJavaScript. فـlogin.open() بلا await يبدأ الانتقال وينفّذ السطر التالي فوراً، فالفحص الذي يقرأ الصفحة مرة واحدة قد يقرأ الصفحة القديمة؛ وإذا كان الاستدعاء غير المنتظَر هو آخر إجراء، فقد تنتهي دالة الاختبار قبل أن ينتهي هو، فلا يفحص شيء في الاختبار نتيجته. ولا يحذّر المترجم من وعد لا ينتظره أحد؛ لكن أداة التدقيق (Linter)، وهي أداة تفحص الشيفرة بحثاً عن أنماط خطرة مثل ESLint، تحذّر منه بقاعدة مثل @typescript-eslint/no-floating-promises، ويمكن أن يكشفه النوع أيضاً كما يبيّن المثال التطبيقي.
الخطأ المرمي هو طريقة فشل الاختبار: expect الفاشل يرمي خطأ، ويسجّله المشغّل نتيجةً. ويجب أن يحترم try/catch/finally ذلك. استخدم finally للتنظيف الذي يجب أن يعمل في الحالتين، وcatch فقط عندما يكون الخطأ هو ما تختبره، ثم أكّد عليه. أما catch الذي يسجّل ويتابع فيحوّل الفحص الفاشل إلى نجاح. والتنظيف الفعلي يحذف في الغالب البيانات التي أنشأها الاختبار؛ أما استدعاء clearCookies أدناه فيبيّن موضع التنظيف فقط، لأن Playwright يبدأ كل اختبار أصلاً دون ملفات تعريف ارتباط من الاختبارات الأخرى.
// Wrong: the navigation is started but not awaited
login.open();
expect(await page.title()).toBe('Sign in'); // may read the previous page
// Wrong: the error is swallowed, so the runner records a pass
try {
await expect(page.getByTestId('message')).toHaveText('Welcome, Sara');
} catch (error) {
console.log('check failed', error); // the test still passes
}
// Right: await every action, let a failed check throw, clean up in finally
await login.open();
try {
await login.signIn('sara@example.com', 'Correct-1');
await expect(page.getByTestId('message')).toHaveText('Welcome, Sara');
} finally {
await page.context().clearCookies(); // runs on pass and on fail
}مثال تطبيقي: ملف اختبار بصيغة ترتيب، تنفيذ، تأكيد
أدناه ملف login.spec.ts الذي يستخدم الصنف LoginPage من قسم الأصناف، مع تسمية كل ميزة في هذا الدرس. ويتبع كل اختبار نمط «ترتيب، تنفيذ، تأكيد» (Arrange-Act-Assert): رتّب الحالة (أنشئ كائن الصفحة وافتح الصفحة)، ونفّذ (سجّل الدخول)، وأكّد (افحص الرسالة). وتنفيذ واحد وتأكيد واحد مركّز في كل اختبار يبقي التقرير مقروءاً.
توصيفات الأنواع (Type Annotations) مثل email: string وqty: number وPromise<void> أنواع (Types) في TypeScript؛ ويُقرأ Promise<T> وعداً سيعطي قيمة من النوع T، وvoid تعني لا قيمة. لا تغيّر هذه التوصيفات شيئاً وقت التشغيل. يفحصها المترجم ويبلّغ عن كل استدعاء لا يطابقها: signIn(c.email, 123) لأن الرقم ليس نصاً، وlogin.signin(...) لأن الدالة غير موجودة، وconst title = page.title(); title.includes('x') لأن title من النوع Promise<string> الذي لا يملك includes؛ أي أن النوع كشف await منسياً. أما Playwright فيحذف الأنواع دون فحصها ويشغّل الاختبارات رغم ذلك، فلا تظهر هذه الأخطاء إلا في محرّر الشيفرة وعند تشغيل npx tsc --noEmit (فحص فقط دون كتابة ملفات)، وهي خطوة تضيفها الفرق قبل تشغيل الاختبارات.
كيف يعمل هذا الملف؟ مشغّل الاختبارات برنامج تشغّله من سطر الأوامر (موضوع الدرس التالي). يبحث عن الملفات التي تطابق نمطاً مثل *.spec.ts ويحمّل كل ملف، فتعمل الشيفرة الموجودة في المستوى الأعلى من الملف: تستدعي الحلقة test(...) ويخزّن المشغّل الدوال. ثم يستدعي كل دالة مع page جديدة، وينتظر الوعد المُعاد، ويسجّل نجاحاً إذا تحقق، أو فشلاً مع الخطأ وتتبّع مكدسه إذا رُفض.
- لمستخدمي Selenium:
importيقابلimportفي Java أوfrom ... importفي Python؛ والصنف بحقوله ومُنشئه موجود في اللغتين (__init__وselfفي Python، ومُنشئ باسم الصنف وthisفي Java)؛ وارتباطات Java وPython متزامنة فلاawaitفيها: كل استدعاء يتوقف حتى يجيب برنامج التشغيل (Driver)، والخطر المقابل هناك هو نسيان انتظار صريح (Explicit Wait).
login.spec.ts: ملف الاختبار
الجزء 1:import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/login-page'; الجزء 2:const cases = [ { email: 'sara@example.com', password: 'Correct-1', message: 'Welcome, Sara' }, { email: 'sara@example.com', password: 'wrong', message: 'Invalid login' }, ]; الجزء 3:for (const c of cases) { الجزء 4:test('login: ' + c.message, async ({ page }) => { const login = الجزء 5:new LoginPage(page); الجزء 6:await login.open(); await login.signIn(c.email, c.password); الجزء 7:await expect(page.getByTestId('message')).toHaveText(c.message); }); }
pages/login-page.ts: كائن الصفحة
import type { Page } from '@playwright/test'; الجزء 8:export class LoginPage { الجزء 9:private readonly page: Page; constructor(page: Page) { this.page = page; } async open(): Promise<void> { await this.page.goto('/login'); } الجزء 10:async signIn(email: string, password: string): Promise<void> { await this.page.getByLabel('Email').fill(email); await this.page.getByLabel('Password').fill(password); await this.page.getByRole('button', { name: 'Sign in' }).click(); } }
- 1الاستيراد: وحدتان برمجيتانحزمة المشغّل تعطي
testوexpect، وملفك أنت يعطي كائن الصفحة. لا شيء عام. - 2بيانات الاختبار: مصفوفة كائناتكائن لكل حالة، فيه المدخلات والرسالة المتوقعة.
- 3
for...of: اختبار لكل كائنيعمل أثناء تحميل الملف ويسجّل اختبارين، كل منهما مسمّى من بياناته. - 4دالة سهمية async كدالة رد نداءيخزّنها المشغّل ويستدعيها لاحقاً مع
pageجديدة؛ وasyncتسمح للجسم باستخدامawait. - 5
new: نسخة من الصنفيشغّل المُنشئ، وتُخزَّنpageفي الحقل. ولكل اختبار نسخته الخاصة. - 6
awaitقبل كل إجراء في المتصفحبدونه يعمل السطر التالي قبل أن تتغير الصفحة: خطأ نسيانawait. - 7التأكيد
expectالفاشل يرمي خطأ، والخطأ هو نتيجة الاختبار. الاختبار هو من يؤكّد النتيجة، لا كائن الصفحة. - 8
export class: كائن الصفحةالاسم الذي يستورده ملف الاختبار؛ والمعتاد صنف واحد في كل ملف، ويحمل الملف اسم الصنف. - 9الحقل والمُنشئتُضبط
this.pageمرة واحدة عندnewوتقرؤها كل دالة؛ وprivate readonlyتمنع أي شيء آخر من تغييرها. - 10دالة async بأنواع محددةمعاملان من النوع
stringونتيجةPromise<void>: يرفض المترجم رقماً هنا، والوعد يخبر المستدعي بأن يضعawaitقبل الاستدعاء.
النسخة النصية
// login.spec.ts
import { test, expect } from '@playwright/test'; // module: the runner
import { LoginPage } from './pages/login-page'; // module: our page object
const cases = [ // test data: one object per case
{ email: 'sara@example.com', password: 'Correct-1', message: 'Welcome, Sara' },
{ email: 'sara@example.com', password: 'wrong', message: 'Invalid login' },
];
for (const c of cases) { // control flow: one test per object
test('login: ' + c.message, async ({ page }) => { // async arrow function as callback
// Arrange
const login = new LoginPage(page); // class -> instance
await login.open(); // await: a browser action
// Act
await login.signIn(c.email, c.password); // typed: (string, string)
// Assert
await expect(page.getByTestId('message')).toHaveText(c.message);
});
}أخطاء شائعة
- استدعاء إجراء في المتصفح بلا
await، ثم التأكيد على صفحة لم تتغير بعد؛ فيصبح الاختبار متذبذباً أو ينجح لسبب خاطئ. - إحاطة
expectبـtry/catchيسجّل الخطأ ويتابع؛ فيبلّغ المشغّل عن نجاح بينما المنتج معطّل. - وضع التأكيدات داخل دوال كائن الصفحة، فيقرر كائن الصفحة النجاح أو الفشل ولا يعود الاختبار يقول ما الذي يفحصه.
- نسخ المحددات في كل ملف اختبار بدلاً من استيراد كائن صفحة واحد؛ فيعني تغيير نموذج تسجيل الدخول عشرين تعديلاً.
- إسكات المترجم بـ
anyأو// @ts-ignore، وهو ما يعطّل بالضبط الفحوص التي تكشف المعاملات الخاطئة والأخطاء الإملائية.
من منظور ضمان الجودة
في الفريق تُراجَع شيفرة الاختبار كما تُراجَع شيفرة المنتج. قبل أن تكتب شيئاً اقرأ الحزمة الموجودة: أين توجد ملفات الاختبار وكائنات الصفحات والبيانات، وأي مشغّل يُستورد، وما اصطلاحات الفريق في التسمية والدوال async والتنظيف. واسأل هل قاعدة التدقيق الخاصة بالوعود غير المنتظَرة مفعّلة؛ وإن لم تكن فاقترحها، لأنها تزيل فئة كاملة من الاختبارات المتذبذبة.
وعندما يفشل اختبار، قرر أولاً ما الذي فشل: المنتج أم الاختبار أم البيئة. يشير تتبّع المكدس من المشغّل إلى سطر؛ فإن كان في كائن صفحة أو ملف اختبار، فاقرأ الشيفرة حوله بأسئلة هذا الدرس: هل انتُظر كل إجراء؟ هل أخفى catch شيئاً؟ هل قال صف البيانات ما توقعه الاختبار؟ وسجّل الجواب في تقرير العيب أو في التعديل الذي يصلح الاختبار.
تمرين عملي
اعثر على العيوب في اختبار التسجيل
ملف الاختبار أدناه يسجّل مستخدمَين ويفحص الرسالة. لا يجتاز فحص الأنواع (npx tsc --noEmit) إلا بعد إصلاح واحد، وفيه عيبان آخران يجعلانه غير موثوق أو غير صادق. اعثر على الثلاثة، واذكر ما يفعله كل منها بالنتيجة، وأصلحها على الورق. ثم سمِّ أجزاء الترتيب والتنفيذ والتأكيد في أحد الاختبارين.
لكائن الصفحة RegisterPage مُنشئ يستقبل page، ويصرّح بالدوال open(): Promise<void> وfillForm(name: string, age: string): Promise<void> وsubmit(): Promise<void>.
import { test, expect } from '@playwright/test';
import { RegisterPage } from './pages/register-page';
const cases = [
{ name: 'Sara', age: 18, message: 'Account created' },
{ name: 'Omar', age: 17, message: 'You must be 18 or older' },
];
for (const c of cases) {
test('register: ' + c.message, async ({ page }) => {
const register = new RegisterPage(page);
register.open();
await register.fillForm(c.name, c.age);
await register.submit();
try {
await expect(page.getByTestId('message')).toHaveText(c.message);
} catch (error) {
console.log('message check failed', error);
}
});
}- اقرأ كل سطر واسأل: هل يعيد هذا الإجراء وعداً؟ وهل يُنتظر؟
- انظر في كل
try/catch: ماذا يحدث للخطأ؟ - قارن كل استدعاء بتوقيع الدالة في كائن الصفحة.
- علّم أسطر الترتيب والتنفيذ والتأكيد في أحد الاختبارين.
إظهار الإجابة النموذجية
خطأ من المترجم: fillForm(c.name, c.age) يمرّر رقماً حيث صُرّح بـage: string. الإصلاح: String(c.age)، أو الأفضل تعديل كائن الصفحة ليقبل age: number ويحوّله داخلياً.
نسيان await: register.open() يبدأ الانتقال ويتابع الاختبار فوراً، فيتسابق fillForm مع تحميل الصفحة: تصبح النتيجة رهن التوقيت، ولا يُرمى خطأ open() في ذلك السطر. الإصلاح: await register.open().
خطأ مبتلَع: يسجّل catch التأكيد الفاشل وينتهي الاختبار بلا خطأ، فيبلّغ المشغّل عن نجاح لعمر حتى لو كانت قاعدة العمر معطّلة. الإصلاح: احذف try/catch؛ وإن احتجت تنظيفاً فاستخدم finally بلا catch.
الترتيب: new RegisterPage(page) وopen(). التنفيذ: fillForm وsubmit. التأكيد: سطر expect. أما الحلقة ومصفوفة البيانات فليستا جزءاً من أي اختبار بعينه؛ بل تولّدان الاختبارين.
أسئلة شائعة
هل أحتاج TypeScript أم تكفي JavaScript؟
تكفي JavaScript لتشغيل الاختبارات، فالمشغّل يقبل اللغتين. وتضيف TypeScript توصيفات أنواع يفحصها المترجم، فتكشف المعاملات الخاطئة والدوال المكتوبة خطأً وبعض حالات نسيان await قبل أن يعمل أي شيء. ويشغّل Playwright ملفات الاختبار المكتوبة بـTypeScript دون إعداد إضافي لكنه لا يفحص أنواعها، فاقرأ هذه الأخطاء في محرّر الشيفرة أو شغّل npx tsc --noEmit. وبقية ميزات هذا الدرس تعمل بالطريقة نفسها في JavaScript.
لماذا نجح اختباري رغم أني نسيت await؟
الاستدعاء غير المنتظَر أعاد وعداً لم ينتظره أحد، فوصلت دالة الاختبار إلى نهايتها بلا خطأ وسجّل المشغّل نجاحاً. وأياً كان ما فعله الإجراء بعد ذلك فلم يفحصه الاختبار. والوعد المنتظَر وحده يبلّغ عن خطئه في السطر الذي سبّبه، فأضف await ليفشل الاختبار أو ينجح للسبب الصحيح.
هل يحتاج كل اختبار إلى كائن صفحة؟
لا. الصفحة التي تلمسها في اختبار واحد يمكن قيادتها مباشرةً. أنشئ كائن صفحة عندما يكرر اختباران التفاعل نفسه، أو عندما تتغير شاشة كثيراً، لتبقى المحددات في ملف واحد. ويتناول درس «نموذج كائنات الصفحات» لاحقاً في هذه الوحدة النمط بعمق.
أعرف Java أو Python، فهل أتعلم TypeScript أيضاً؟
المفاهيم تنتقل: الوحدات البرمجية والأصناف والمُنشئات وكائنات الصفحات تبدو متشابهة في Java وPython، ولدى Selenium ارتباطات رسمية لكلتيهما. تعلّم لغة إطار العمل الذي يستخدمه فريقك؛ والفرق الأهم الذي يجب فهمه أن إجراءات المتصفح في Playwright وفي ارتباط JavaScript الخاص بـSelenium غير متزامنة وتحتاج await. أما Cypress، وهو مكتوب بـJavaScript أيضاً، فيضع أوامره في طابور ولا يُستخدم مع await.
المراجع
- ECMAScript® Language Specification (ECMA-262)معيار لغة JavaScript، بما فيه قواعد RegExp ودلالاتها، وJSON.parse/JSON.stringify، وكائن Date.
- JavaScript — MDNدليل MDN ومرجعه للغة JavaScript: صياغة اللغة وأنواعها وكائناتها ومكوّناتها المدمجة كما تنفّذها المتصفحات.
- Playwright Documentationالتوثيق الرسمي لـ Playwright، إطار الاختبار الشامل الذي يشغّل Chromium وWebKit وFirefox.
- Page object models — Seleniumنمط كائن الصفحة (Page Object): صنف لكل صفحة أو مكوّن يخفي المحدِّدات ويعرض الإجراءات التي يحتاجها الاختبار، فيقل التكرار.
تدرّب بأدوات متقن
تجميل كود JavaScript وTypeScript بمسافات بادئة وتباعد متسقين.
ولّد سجلات مستخدمين وهمية (الاسم واسم المستخدم والبريد الإلكتروني وكلمة المرور وبذرة الصورة الرمزية والدور) لحسابات الاختبار.
تحويل بيانات CSV أو TSV إلى مصفوفة JSON من الكائنات.
توليد كود جاهز للتنفيذ لطلبات REST API بلغات cURL وJavaScript fetch وaxios وPython requests وJava وC# وGo وPHP.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.