أساسيات JavaScript للمختبِرين
JavaScript هي لغة البرمجة التي يشغّلها المتصفح داخل صفحات الويب. تمنح HTML الصفحةَ بنيتها، وCSS مظهرها، وJavaScript سلوكها. فعندما تنقر «أضف إلى السلة» فيتغيّر الرقم على أيقونة السلة من 2 إلى 3 دون إعادة تحميل، تكون دالة JavaScript قد عملت بسبب نقرتك، وأرسلت طلباً إلى الخادم، وانتظرت الرد، ثم حدّثت الصفحة.
لا تحتاج إلى كتابة JavaScript لتختبر تطبيقات الويب، لكنك تحتاج إلى قراءة بضعة أسطر منها، وتتبّع ما يجري بين النقرة والنتيجة، وقراءة الأخطاء التي يعرضها المتصفح عندما تفشل الشيفرة.
لماذا يهم
في تطبيقات الويب الحديثة يمرّ معظم ما يفعله المستخدمون عبر JavaScript، وعندما تفشل تبقى الصفحة في الغالب سليمة المظهر: زر لا يفعل شيئاً، أو مؤشر تحميل لا يتوقف، أو سعر يظهر NaN. وكثيراً ما يكون الدليل الوحيد رسالة حمراء في وحدة التحكم (Console) بالمتصفح، وهي رسالة لا يراها المستخدم. والمختبِر الذي لا يفتح وحدة التحكم يفوته هذا الإخفاق، أو يكتب تقارير مثل «الزر لا يعمل» لا يستطيع المطور أن يبني عليها شيئاً.
كما تجعل JavaScript التوقيت جزءاً من السلوك: فكثير من العيوب لا يظهر إلا على شبكة بطيئة، أو بعد استجابة خطأ، أو بعد نقرة ثانية.
المتغيّرات والقيم والدوال
المتغيّر (variable) اسمٌ لمكان يحفظ قيمة: const total = 150; لا يمكن إعادة إسناده، وlet qty = 1; يمكن. وللقيم أنواع: النصوص ('150') والأرقام (150) والقيم المنطقية (true) والكائنات ({ price: 25 }) والمصفوفات ([3, 5])، وundefined (لم تُضبط قيمة) أو null (فارغ عن قصد). أما الدالة (function) فمجموعة خطوات لها اسم تعمل عندما يستدعيها شيء ما، وقراءتها تكشف غالباً الحالات التي نسيها المطور، مثل صنف في السلة بلا منتج في المثال أدناه.
'5' + 1تعطي'51': فإذا كان أحد الطرفين نصاً ضمّ العامل+الطرفين بدلاً من جمعهما، والقيمة المقروءة من حقل نصي تبقى نصاً حتى تحوّلها الشيفرة. فقد تعرض السلة 51 صنفاً بدلاً من 6.'abc' * 2تعطيNaN(ليس رقماً)، وأي عملية حسابية فيهاNaNنتيجتهاNaN، وقد تظهر على الشاشة في مكان السعر.- القيمة المفقودة حين تُستخدم نصاً تظهر بكلمة «undefined» أو بعنوان فارغ.
// cart.js: the kind of function you will read in a page
function lineTotal(item) {
const price = item.product.price; // a number, e.g. 25
const qty = Number(item.qty); // the field gives a string, e.g. '2'
return price * qty;
}
lineTotal({ product: { price: 25 }, qty: '2' }); // 50
lineTotal({ product: undefined, qty: '2' }); // throws a TypeErrorالأحداث والشيفرة غير المتزامنة
معظم شيفرة الصفحة ينتظر الأحداث. تربط الشيفرة معالجاً (handler) بعنصر، مثل button.addEventListener('click', applyCoupon)، ويستدعيه المتصفح كلما وقع الحدث. ومن الأحداث الشائعة click وinput وchange وsubmit وkeydown. ولا يعمل المعالج إلا لأحداثه: فالفحص الذي يستمع لضغطات المفاتيح لا يعمل أبداً عندما تُلصق القيمة بالفأرة أو يملؤها الملء التلقائي.
التواصل مع الخادم يستغرق وقتاً، ولذلك تكون شيفرته غير متزامنة (asynchronous). ترسل fetch() طلب HTTP وتعيد فوراً وعداً (Promise)، أي كائناً يمثّل نتيجة لم تصل بعد. يبقى الوعد معلّقاً (pending) حتى يتحقّق (fulfilled) بقيمة أو يُرفض (rejected) بخطأ؛ وفي أثناء ذلك تتوقف الشيفرة التي تستخدم await داخل دالتها غير المتزامنة (async)، وتبقى بقية الصفحة قابلة للاستخدام.
وعندما يصل الرد يتحقّق الوعد بكائن استجابة يُسمّى عادةً res: تكون قيمة res.ok هي true لرموز الحالة من 200 إلى 299 وحدها، وتقرأ await res.json() جسم الاستجابة (body) بصيغة JSON، وهي الصيغة النصية التي تستخدمها معظم الواجهات البرمجية (APIs) لنقل البيانات، وتُكتب على نحو يشبه كائن JavaScript. ولا يُرفض وعد fetch() إلا عندما يفشل الطلب نفسه، كخطأ في الشبكة. أما استجابة 404 أو 500 فتحقّقه، فيجب أن تفحص الشيفرة res.ok.
fetch() أيضاً، فيُطلق الجسم غير المفحوص TypeError ويبقى مؤشر التحميل يدور.قسيمة صالحة: 200 OK
- المستخدم
- شيفرة الصفحة JavaScript في المتصفح
- الخادم
- 1 من المستخدم إلى شيفرة الصفحة: ينقر «تطبيق القسيمة» يستدعي الحدث
clickالمعالج - 2 شيفرة الصفحة داخليًا: يُظهر مؤشر التحميل
- 3 من شيفرة الصفحة إلى الخادم: يرسل الطلب تعيد
fetch()وعداً معلّقاً فوراً، وتبقى الصفحة قابلة للاستخدامPOST /api/coupons/apply - 4 من الخادم إلى شيفرة الصفحة:
200 OKردّ جسم JSON فيه الخصم - 5 شيفرة الصفحة داخليًا: تحقّق الوعد: تقرأ
data.discount.percentالشيفرة لا تفحصres.ok، لكن هذا الجسم فيه خصم - 6 من شيفرة الصفحة إلى المستخدم: إجمالي جديد، واختفاء مؤشر التحميل ردّ
النتيجة: تعمل الشيفرة، فينجح اختبار يقتصر على القسائم الصالحة ويبقى العيب مختبئاً. نجاح
قسيمة منتهية: 422
- المستخدم
- شيفرة الصفحة JavaScript في المتصفح
- الخادم
- 1 من المستخدم إلى شيفرة الصفحة: ينقر «تطبيق القسيمة» يُظهر المعالج مؤشر التحميل
- 2 من شيفرة الصفحة إلى الخادم: يرسل الطلب
POST /api/coupons/apply - 3 من الخادم إلى شيفرة الصفحة: 422: القسيمة مرفوضة ردّ
{"error":"COUPON_EXPIRED"} - 4 شيفرة الصفحة داخليًا: الوعد يتحقّق رغم ذلك: 422 استجابة ولأنها لا تفحص
res.ok، تقرأ الشيفرةdata.discount.percent - 5 شيفرة الصفحة داخليًا: يُطلَق TypeError ولا يلتقطه شيء تتوقف الدالة عنده، ولا يُنفَّذ السطر الذي يخفي مؤشر التحميل فشل
- 6 من شيفرة الصفحة إلى المستخدم: مؤشر التحميل يدور بلا رسالة ردّ الأثر الوحيد عبارة «Uncaught (in promise)» في وحدة التحكم
النتيجة: يعلق المستخدم؛ وخطأ وحدة التحكم واستجابة 422 هما دليل تقرير الخطأ. فشل
لا يُرفض وعد fetch() إلا عندما يفشل الطلب نفسه، كخطأ في الشبكة؛ أما حالة الخطأ مثل 422 أو 500 فتحقّقه، فيجب أن تفحص الشيفرة res.ok.
- يستطيع المستخدم أن يتصرف والطلب معلّق: ينقر مرة أخرى، أو يعدّل الحقل، أو يغادر الصفحة. افحص الطلبات المكررة وحالة الانتظار.
- قد تصل الردود بغير ترتيبها: ففي مربع البحث قد يحلّ ردّ بطيء على «ri» محلّ الردّ الأحدث على «riyadh».
- اختبر على الأقل النجاح، وحالة الخطأ، وغياب الرد تماماً (انقطاع الشبكة أو انتهاء المهلة)؛ والرد البطيء يكشف حالة الانتظار.
جرّبها: محاكي رموز حالة API جرّبها: مولّد استجابات خطأ وهمية
كيف تُعطّل JavaScript الصفحة
عندما تُطلق الشيفرة استثناءً (exception) ولا يلتقطه شيء، يتخلّى المتصفح عن الشيفرة التي كانت تعمل، أي تلك الدالة وكل دالة استدعتها، ويسجّل الخطأ في وحدة التحكم. وتواصل المعالجات والشيفرات الأخرى عملها، فتبقى بقية الصفحة تعمل في الغالب، ولا تصمت إلا الميزة التي اصطدمت بالخطأ. وبمصطلحات ISTQB فما تلاحظه إخفاق (failure)، سببه عيب (defect) أدخله شخص بخطأ (error) بشري؛ أما JavaScript فتسمّي استثناءاتها أخطاءً فحسب. ومن صور الإخفاق المعتادة:
- زر لا يفعل شيئاً: أطلق معالجه استثناءً قبل أن يغيّر أي شيء.
- مؤشر تحميل لا يتوقف أو زر يبقى معطّلاً: السطر الذي يعيدهما إلى حالتهما يأتي بعد السطر الذي فشل.
- قيم غريبة، وكثيراً بلا أي خطأ، لأن الشيفرة عملت حتى نهايتها ببيانات خاطئة:
NaNأوundefinedأو[object Object]، أو 51 بدلاً من 6. - جزء كامل من الصفحة مفقود: ملف الشيفرة الخاص به لم يُحمَّل أو فيه خطأ في الصياغة، فلم يعمل شيء منه.
- إخفاق مع بعض البيانات فقط: قائمة فارغة، أو حقل اختياري مفقود، أو منتج محذوف.
قراءة خطأ في وحدة التحكم
افتح أدوات المطوّر (F12 في معظم متصفحات الحاسوب) واختر لوحة Console؛ وسيتناول درس أدوات المطوّر بقية اللوحات. امسحها، وكرّر الإجراء، واقرأ الأخطاء الحمراء الجديدة وحدها. وفيما يلي ما تسجّله الدالة lineTotal من القسم الأول، بصياغة Chrome، عندما يكون في السلة صنف بلا منتج.
- النوع:
TypeError(عملية على نوع خاطئ من القيم، وغالباًundefined)، وReferenceError(اسم غير معروف)، وSyntaxError(شيفرة أو JSON يتعذّر تحليلها)، وRangeError(قيمة خارج النطاق). - الرسالة: هنا قراءة
priceمنundefined، أي أنitem.productمفقود. اسأل أي بيانات سبّبت ذلك، كمنتج حُذف من الكتالوج. وتختلف الصياغة بين المتصفحات، أما النوع فلا. - الموضع: الملف والسطر والعمود،
cart.js:58:30. - تتبّع المكدّس (Stack Trace): موضع إطلاق الخطأ في الأعلى، وتحته كل من استدعاه. اقرأه من الأسفل لتعرف القصة (نقرة زر شغّلت معالجاً بلا اسم، فاستدعى
renderCartثمlineTotal). وتعنيUncaughtأن شيئاً لم يلتقط الخطأ، وتعني(in promise)أنه جاء من وعد مرفوض. - تُصغَّر الشيفرة (minify) في المواقع الحية عادةً، وكثيراً ما تصير سطراً واحداً طويلاً (
app.3f9c1.js:1:20431)؛ وتعيد خرائط المصدر (source maps) المطورين إلى الملف الأصلي، ويجعلها المنسِّق مقروءة لك.
Uncaught TypeError: Cannot read properties of undefined (reading 'price')
at lineTotal (cart.js:58:30)
at renderCart (cart.js:31:17)
at HTMLButtonElement.<anonymous> (cart.js:12:5)مثال تطبيقي: زر «تطبيق القسيمة» الذي لا ينتهي
في اختبار استكشافي لصفحة الدفع، تترك القسيمة المنتهية WELCOME10 مؤشر التحميل يدور بلا نهاية، بينما تعمل القسيمة الصالحة. يمسح المختبِر وحدة التحكم، ويكرّر الخطوات، ويجمع الأدلة أدناه.
- رفض الخادم القسيمة كما ينبغي، بالحالة
422(Unprocessable Content: الطلب سليم الصياغة، لكن الخادم لا يقبل معالجة مضمونه) ورمز خطأ. - ومع ذلك تحقّق وعد
fetch()، والشيفرة لا تفحصres.ok. وجسم الخطأ ليس فيهdiscount، فيُطلق السطر 42 استثناءTypeError، ولا يُنفَّذ أبداً السطر 43 الذي يخفي مؤشر التحميل. - التقرير: «القسيمة المنتهية تترك مؤشر التحميل يدور ولا تعرض رسالة»، مع الخطوات، والنتيجة المتوقعة (اختفاء مؤشر التحميل ورسالة تفيد بانتهاء القسيمة)، والنتيجة الفعلية، ونص وحدة التحكم، والطلب والاستجابة. ثم اختبر بقية النتائج: رمز غير معروف، وخطأ في الخادم، ورد بطيء، وانقطاع الشبكة، ونقرة مزدوجة.
Console
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'percent')
at applyCoupon (checkout.js:42:44)
Network
POST /api/coupons/apply -> 422 {"error":"COUPON_EXPIRED"}
checkout.js
38 async function applyCoupon() {
39 spinner.hidden = false;
40 const res = await fetch(url, options);
41 const data = await res.json();
42 total.textContent = format(data.discount.percent);
43 spinner.hidden = true;
44 }أخطاء شائعة
- الاختبار ووحدة التحكم مغلقة، والإبلاغ بأن «الزر لا يعمل» دون نص الخطأ.
- لصق السطر الأول من الخطأ فقط أو لقطة شاشة له: انسخ النوع والرسالة وتتبّع المكدّس نصاً.
- اختبار الاستجابات السريعة الناجحة وحدها، مع أن حالات الخطأ والردود البطيئة وانقطاع الشبكة والنقرات المزدوجة هي حيث تنكسر الشيفرة غير المتزامنة.
- تسجيل كل رسالة في وحدة التحكم عيباً، مع أن بعضها يأتي من إضافات المتصفح أو من شيفرات طرف ثالث؛ فتحقّق من الملف أولاً.
- جعل خطأ وحدة التحكم عنواناً لتقرير الخطأ بدلاً من وصف الإخفاق الذي يراه المستخدم.
من منظور ضمان الجودة
أبقِ وحدة التحكم مفتوحة وأنت تختبر ميزة ويب، وامسحها قبل كل سيناريو، وتقصَّ كل خطأ أحمر جديد حتى لو بدت الشاشة سليمة. سجّل الإجراء والنص كاملاً والطلب الذي سبقه مباشرة؛ فبالملف والسطر يجد المطور العيب غالباً بسرعة.
وفي المراجعات، اسأل عن كل إجراء يستدعي الخادم: ما الاستجابات الممكنة، وما الذي يجب أن تفعله الصفحة في كل منها: النجاح، وخطأ في صحة المدخلات، وخطأ الخادم، وانتهاء المهلة، وانقطاع الشبكة، والنقرة الثانية. تصبح الإجابات شروط الاختبار لديك، وتعود أسئلة التوقيت نفسها لاحقاً في صورة الانتظار في أتمتة واجهة المستخدم.
تمرين عملي
اقرأ الخطأ خلف زر «تحميل المزيد»
في صفحة سجل الطلبات، لا يُظهر زر «تحميل المزيد» إلا مؤشر تحميل عند تحميل الصفحة 3، بينما تعمل الصفحتان 1 و2. حلّل الأدلة أدناه كما تفعل قبل كتابة تقرير الخطأ.
Console (Chrome)
Uncaught (in promise) SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
Network
GET /api/orders?page=3 -> 502 Bad Gateway Content-Type: text/html
orders.js
12 async function loadMore(page) {
13 spinner.hidden = false;
14 const res = await fetch('/api/orders?page=' + page);
15 const orders = await res.json();
16 renderOrders(orders);
17 spinner.hidden = true;
18 }- سمِّ نوع الخطأ والسطر الذي لا بد أنه أطلقه.
- اشرح لماذا تذكر الرسالة
<، ولماذا لم يُرفض وعدfetch(). - صِف ما يراه المستخدم والأسطر التي لا تُنفَّذ أبداً.
- حدّد ما ستبلغ عنه ولمن، واكتب أربعة اختبارات إضافية.
إظهار الإجابة النموذجية
SyntaxError: شيء ما تعذّر تحليله. والسطر الوحيد الذي يحلّل شيئاً هو السطر 15، أي res.json()؛ وتؤكد Uncaught (in promise) أنه وعد مرفوض لم يعالجه شيء.
تصدر الحالة 502 Bad Gateway عن بوابة (gateway) أو وكيل (proxy)، أي خادم يقف أمام الواجهة البرمجية، عندما يتلقى رداً غير صالح من الخادم الذي خلفه. والجسم هنا صفحة خطأ HTML من تلك البوابة (Content-Type: text/html) تبدأ بـ <!DOCTYPE، فيفشل تحليل JSON عند <. والحالة 502 استجابة في النهاية، فتحقّق وعد fetch()، والشيفرة لا تفحص res.ok.
مؤشر تحميل لا يتوقف ولا رسالة. والسطران 16 و17 لا يُنفَّذان أبداً: لا تُضاف طلبات ولا يُخفى مؤشر التحميل.
تقريران: إلى فريق الواجهة الخلفية أو فريق التشغيل، GET /api/orders?page=3 يعيد 502، مع وقت الطلب؛ وإلى مطور الواجهة الأمامية، التحميل الفاشل لا يعرض رسالة ويترك مؤشر التحميل، والمتوقع رسالة واضحة وزر قابل للاستخدام. ومن الاختبارات الإضافية مثلاً: استجابة 500 واستجابة 504، ورد بطيء، ووضع عدم الاتصال، وصفحة أخيرة فارغة، ونقرة مزدوجة على «تحميل المزيد».
أسئلة شائعة
هل أحتاج إلى تعلّم البرمجة بلغة JavaScript لأختبر تطبيقات الويب؟
ليس للبداية. فقراءة المتغيّرات والدوال والأحداث والوعود وفهم أخطاء وحدة التحكم تكفي للاختبار اليدوي للويب. أما أتمتة اختبارات المتصفح باستخدام Cypress فتتطلب كتابة JavaScript أو TypeScript، وكذلك Playwright Test، مشغّل الاختبارات الخاص بأداة Playwright (ولأداة Playwright أيضاً إصدارات للغات Python وJava و.NET)؛ وهذا الدرس هو الأساس لتلك الدروس.
هل JavaScript هي Java نفسها؟
لا. هما لغتان مختلفتان في التصميم، والتشابه في الاسم تاريخي. وJavaScript موحّدة باسم ECMAScript في المعيار ECMA-262، أما TypeScript فهي JavaScript مضافاً إليها تعريفات الأنواع، وتُحوَّل إلى JavaScript قبل أن يشغّلها المتصفح.
هل كل رسالة حمراء في وحدة التحكم عيب؟
ليس بالضرورة. فاللون الأحمر يميّز الأخطاء، ومنها طلبات الشبكة الفاشلة، وبعضها يأتي من إضافات المتصفح أو من شيفرات طرف ثالث مثل أدوات التحليلات. وتكون عيباً عندما تأتي من شيفرة المنتج أو طلباته وتؤثر في السلوك، أو عندما تنص قاعدة الفريق على وحدة تحكم نظيفة. وإذا لم تكن متأكداً، فسجّل الملف والإجراء واسأل المطور.
المراجع
- ECMAScript® Language Specification (ECMA-262)معيار لغة JavaScript، بما فيه قواعد RegExp ودلالاتها، وJSON.parse/JSON.stringify، وكائن Date.
- Fetch Standardيعرّف CORS: طلبات التحقق المسبق وترويسات Access-Control-* والاستجابات التي يسمح المتصفح للصفحة برؤيتها.
- ISTQB Glossaryالتعريفات القياسية لمصطلحات الاختبار — حالة الاختبار واختبار الانحدار والخطورة والأولوية وتحليل القيم الحدّية وغيرها.
- JavaScript — MDNدليل MDN ومرجعه للغة JavaScript: صياغة اللغة وأنواعها وكائناتها ومكوّناتها المدمجة كما تنفّذها المتصفحات.
تدرّب بأدوات متقن
تجميل كود JavaScript وTypeScript بمسافات بادئة وتباعد متسقين.
احصل على نقطة نهاية عامة تعيد أي رمز حالة HTTP، مع تأخير اختياري وجسم JSON مخصّص، لاختبار معالجة الأخطاء في العميل.
ولّد استجابات خطأ منظّمة (التحقق والمصادقة وعدم العثور وتحديد المعدّل وخطأ الخادم) بالصيغ الشائعة.
ولّد استجابات API وهمية كاملة (الحالة والترويسات وجسم JSON) لحالات النجاح والقوائم والتفاصيل والأخطاء والنتائج الفارغة.
التحقق من صحة صياغة JSON مع تحديد مواقع الأخطاء بدقة واقتراحات مفيدة للإصلاح.
ابحث عن أي رمز حالة HTTP لمعرفة معناه ومرجعه في RFC ومتى يُستخدم.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.