نموذج كائنات المستند (DOM)
نموذج كائنات المستند (Document Object Model)، ويُختصر DOM، هو النموذج الحي الذي يبنيه المتصفح لصفحة الويب في الذاكرة. فعندما يحلّل المتصفح شيفرة HTML يبني شجرة من العُقد (Nodes): المستند في الجذر، وعقدة عنصر لكل عنصر، وعُقد نصية للكلمات، ثم يتيح لـJavaScript واجهة لقراءة هذه الشجرة وتغييرها. والمتصفح يرسم الشاشة من DOM ومن CSS، لا من ملف HTML.
مثال ملموس: صفحة سلة تُرسل <input id="qty" value="1">. فيضمّ DOM عنصر إدخال قيمة سمته value هي 1. وعندما يغيّر المستخدم الكمية إلى 3 تصبح الخاصية value للعنصر 3، بينما تبقى السمة 1، ولا يتغيّر مصدر الصفحة إطلاقاً. فالمصدر هو الصفحة كما أُرسلت، وDOM هو الصفحة كما هي الآن.
لماذا يهم
كل ما يفعله اختبار واجهة المستخدم يمرّ عبر DOM. فالمحدِّد مثل #qty استعلام على الشجرة الحالية، والنقرة تُسلَّم إلى عقدة عنصر، والتأكيد يقرأ خاصيةً أو نص عقدة. وإذا لم تعرف ما كان في DOM في تلك اللحظة، فلن تستطيع أن تحكم هل كشف الاختبار الفاشل عيباً، أم استخدم محدِّداً خاطئاً، أم بحث قبل الأوان.
ويهمّ ذلك في الاختبار اليدوي أيضاً. فبعض العيوب لا تظهر إلا في DOM: رسالة تُضاف لكنها تبقى مخفية، وحقل غير مربوط بتسميته، وزرّ موجود مرتين. وقراءة DOM تتيح لك أن تبلّغ عن السبب، أي العنصر وسماته وحالته، لا عن العَرَض وحده، وأن تتحقق هل المشكلة في الصفحة أم في اختبارك.
شجرة من العُقد
رأيت في درس كيف تعمل المتصفحات أن المتصفح يحلّل HTML. وناتج هذه الخطوة هو DOM: شجرة كل ما فيها عقدة. فعقدة المستند (Document) هي الجذر، وتصل إليها السكربتات باسم document. وكل عنصر HTML يصبح عقدة عنصر (Element)، أي فرعاً يمكن أن يحتوي عُقداً أخرى. والنص داخل العناصر يصبح عُقداً نصية (Text)، وهي أوراق الشجرة. وتعليقات HTML تصبح عُقد تعليق (Comment)، فهي موجودة في DOM مع أن شيئاً لا يعرضها.
السمات ليست أبناءً في الشجرة. فالسمة id="qty" محفوظة على عنصر الإدخال نفسه، كبطاقة ملصقة على صندوق لا كغرض بداخله. كما أن المسافات وفواصل الأسطر بين الوسوم تصبح عادةً عُقداً نصية، ولهذا يجد السكربت الذي يعدّ العُقد الأبناء لعنصرٍ ما عدداً أكبر مما توحي به الوسوم.
- الأب والابن (Parent وChild): في النموذج أدناه، العنصر
formهو أب العناصرlabelوinputوbuttonوp، وهي أبناؤه. - الأشقاء (Siblings): أبناء الأب نفسه، مثل
labelوinput. - السلف والخلَف (Ancestor وDescendant): العنصر
bodyسلفٌ لعنصرinput، وكل ما داخل النموذج من خلَف النموذج. - والمحددات تستخدم العلاقات نفسها. ففي CSS يعني المحدِّد
form > inputعنصر إدخال هو ابن مباشر لنموذج، ويعنيform inputأي عنصر إدخال من خلَفه. وفي XPath تنتقل/إلى ابن، وتبحث//في كل الخلَف (وفي بداية التعبير في المستند كله)، ويُبقي الشرط[@id='qty']العناصر التي لسمتها هذه القيمة فقط، وتصعد..إلى الأب: فالتعبير//input/..ينتقل من عنصر الإدخال إلى أبيه.
<form id="cart">
<label for="qty">Quantity</label>
<input id="qty" value="1">
<button type="submit">Update</button>
<p class="msg" hidden>Cart updated</p>
</form>value="1" محفوظة على عنصرها لا داخله.نموذج السلة في DOM
الجزء 1:#document └ html ├ head └ body └ الجزء 2:form id="cart" ├ label for="qty" │ └ الجزء 4:"Quantity" ├ input الجزء 3:id="qty" value="1" ├ button type="submit" │ └ الجزء 4:"Update" └ p class="msg" الجزء 5:hidden └ الجزء 4:"Cart updated"
- 1عقدة المستندالجذر، واسمه
documentفي السكربتات: كل بحث عن عنصر يبدأ منه أو من عنصر وُجد قبلاً - 2عقدة عنصرواحدة لكل عنصر، من
htmlإلىp. والعنصرformأبٌ لأربعة أبناء هم أشقاء فيما بينهم - 3السماتمحفوظة على العنصر، وليست أبناءً في الشجرة. بعد أن يغيّر المستخدم الكمية إلى 3 تصبح الخاصية
value"3" بينما تبقى هذه السمة "1" - 4العُقد النصيةالكلمات التي يقرؤها المستخدم، وهي أوراق بلا أبناء، وتطابقها المحددات النصية
- 5موجود لكنه غير معروضالسمة
hiddenتُبقي الفقرة خارج الشاشة، لكن الاستعلام يجدها مع ذلك: فالموجود ليس بالضرورة مرئياً
حُذفت أبناء head والعُقد النصية التي لا تحوي إلا مسافات بين الوسوم.
السمات والخصائص
السمة (Attribute) تُكتب في HTML، مثل value="1". أما الخاصية (Property) فتنتمي إلى كائن JavaScript الذي يمثّل العنصر، مثل qty.value. ويُبقي المتصفح الاثنين متطابقين في كثير من السمات، ويسمّي معيار HTML ذلك الانعكاس (Reflecting): فتغيير الخاصية id يغيّر السمة id والعكس. ولبعض الأزواج أسماء مختلفة: فالسمة class تقابلها الخاصية className، والسمة for تقابلها htmlFor.
أما الأزواج الأهم للمختبِر فهي التي لا تبقى متطابقة:
valueفي حقل الإدخال: السمة هي القيمة الابتدائية، ويمكن قراءتها أيضاً من الخاصيةdefaultValue. أما الخاصيةvalueفهي ما يحويه الحقل الآن. والكتابة في الحقل تغيّر الخاصية وحدها.checkedفي مربع الاختيار: السمة تقول هل يبدأ محدَّداً (defaultChecked)، والخاصيةcheckedتقول هل هو محدَّد الآن.hrefفي الرابط: يُعيدgetAttribute('href')ما كُتب في HTML، مثل/help، بينما تُعيد الخاصيةhrefالعنوان الكامل الذي حلّله المتصفح، مثلhttps://shop.example/help.- السمات المنطقية (Boolean) مثل
disabledوrequired: العبرة بوجودها فقط. فالسمةdisabled="false"تعطّل الزر مع ذلك، لأن السمة موجودة. والسمةhiddenتعمل بالطريقة نفسها: فبأي قيمة، حتىhidden="false"، يبقى العنصر مخفياً.
// After the user changed the quantity from 1 to 3
const qty = document.querySelector('#qty');
qty.getAttribute('value'); // "1" attribute: what the HTML said
qty.defaultValue; // "1" property that reflects the attribute
qty.value; // "3" property: what the field holds nowكيف تغيّر JavaScript شجرة DOM
لغة JavaScript التي تعرّفت عليها في الدرس السابق تغيّر الصفحة بتغيير DOM، ثم يعيد المتصفح رسم الشاشة من الشجرة الجديدة. ويكاد كل تغيير يكون واحدة من عمليات قليلة: إيجاد عقدة، أو إنشاء عقدة، أو إدراجها، أو حذفها، أو تغيير نصها أو سماتها أو خصائصها. وتجد السكربتات العناصر عادةً عبر document.querySelector()، الذي يأخذ محدِّد CSS ويُعيد أول عنصر مطابق، أو null إن لم يطابقه شيء، أما querySelectorAll() فيُعيد كل التطابقات.
وأُطر العمل مثل React وAngular وVue تُجري الاستدعاءات نفسها نيابةً عنك. وعندما تتغيّر البيانات قد تحذف عنصراً وتُدرج عنصراً جديداً مكانه، فالعنصر الذي وجده اختبارك قبل لحظة قد لا يكون في الشجرة بعد الآن. عندها يفشل Selenium بالخطأ stale element reference، أما Playwright فيتجنّب معظم هذه الحالات لأنه يحدّد عنصراً محدَّثاً في كل مرة يُستخدم فيها المحدِّد.
- مصدر الصفحة وDOM مختلفان. فأمر عرض مصدر الصفحة (View Page Source، أو Ctrl+U) يعرض ما أرسله الخادم، أما DOM فيضم أيضاً كل ما أضافته السكربتات بعد ذلك. وفي تطبيق الصفحة الواحدة (Single-Page Application) قد لا يزيد المصدر كثيراً على حاوية فارغة مثل
<div id="root"></div>. - قد يصل المحتوى بعد ظهور الصفحة. فعندما يجلب سكربت بيانات ثم يُدرجها، لا يكون العنصر موجوداً في البداية، فعلى الاختبار أن ينتظره.
- المحلِّل (Parser) يغيّر الشجرة أيضاً. فصف الجدول المكتوب مباشرةً داخل
<table>يوضع داخل عنصر<tbody>يُدرجه المحلّل، لذا لا يطابق تعبير XPath المنسوخ من المصدر، مثل//table/tr، أي شيء. - النص المُدرج عبر
innerHTMLيُحلَّل على أنه شيفرة HTML، أماtextContentفيُدرجه نصاً عادياً. فإذا ظهر اسم كُتب بالشكل<b>Ali</b>بخط عريض في مكانٍ ما، فمدخلات المستخدم تصل إلى DOM على أنها شيفرة: أبلغ عن ذلك، لأن المسار نفسه قد يسمح بثغرة البرمجة عبر المواقع (XSS).
const form = document.querySelector('#cart'); // find an element
const msg = form.querySelector('.msg');
msg.textContent = 'Cart updated: 3 items'; // replace its text
msg.hidden = false; // removes the hidden attribute
const note = document.createElement('p'); // create a node, not in the tree yet
note.textContent = 'Free delivery';
form.append(note); // insert it: now it can be seen
note.remove(); // remove it againفحص DOM، ولماذا تعتمد عليه المحددات
انقر بزر الفأرة الأيمن على عنصر واختر Inspect (فحص)، فتُفتح أدوات المطور على لوحة Elements (وفي Firefox لوحة Inspector) وقد حُدِّد ذلك العنصر. تعرض هذه اللوحة DOM الحي لا المصدر: فالعناصر التي تضيفها السكربتات تظهر فيها، والمحذوفة تختفي، والسمات تتحدّث عند تغيّرها. لكن شجرتها تعرض السمات لا الخصائص، فبعد أن تغيّر الكمية إلى 3 تظل تعرض value="1". ولقراءة خاصية اكتب $0.value في لوحة Console، فالرمز $0 يشير إلى العنصر المحدَّد في لوحة Elements.
tbody الذي أدرجه المحلّل، وشجرة Elements تُظهرهما لكنها تعرض السمة value، فمن بين الثلاثة لا يقرأ ما كتبه المستخدم إلا Console عبر $0.value.عرض مصدر الصفحةCtrl+U
شيفرة HTML كما أرسلها الخادم
- العناصر التي أضافتها السكربتات:
- لا
- عنصر
tbodyأدرجه المحلّل: - لا
- ما كتبه المستخدم في حقل:
- لا
- استخدمه من أجل:
- رؤية ما سلّمه الخادم
لوحة Elements
DOM الحي: العُقد وسماتها الآن
- العناصر التي أضافتها السكربتات:
- نعم
- عنصر
tbodyأدرجه المحلّل: - نعم
- ما كتبه المستخدم في حقل:
- لا: تعرض السمة
value - استخدمه من أجل:
- كتابة المحددات وتصحيحها
لوحة Console$0
أي خاصية للعنصر المحدَّد في لوحة Elements
- العناصر التي أضافتها السكربتات:
- نعم
- عنصر
tbodyأدرجه المحلّل: - نعم
- ما كتبه المستخدم في حقل:
- نعم:
$0.value - استخدمه من أجل:
- قراءة الخصائص وعدّ تطابقات المحدِّد
كل محدِّد استعلامٌ على هذه الشجرة الحية. فمحددات CSS وXPath تطابق أسماء العناصر وسماتها ومواقعها، والمحددات النصية تطابق نص العُقد، والمحددات المبنية على الدور مثل getByRole في Playwright تستخدم دوراً واسماً ميسَّراً (Accessible Name) يُحسبان من العنصر وسماته ونصه ومن عناصر مرتبطة به مثل تسميته <label>. لذلك تكتب المحددات وتصحّحها على DOM في الحالة التي سيصادفها الاختبار، لا على مصدر الصفحة أبداً.
- عُدّ التطابقات قبل أن تعتمد على محدِّد: يجب أن يُعيد
document.querySelectorAll('#cart button').lengthفي لوحة Console القيمة 1. وفي Chrome وEdge يقبل البحث بالاختصار Ctrl+F في لوحة Elements أيضاً محدِّد CSS أو تعبير XPath. - افحص في الحالة الصحيحة: بعد تحميل البيانات، ومع فتح مربع الحوار، وبكل لغة تعمل بها حزمة الاختبارات.
- الموجود ليس بالضرورة مرئياً. فالعنصر
pالذي يحملhiddenموجود في DOM فيجده الاستعلام، لكن المستخدم لا يراه. وإذا كان المتطلب أن يرى المستخدم شيئاً فتأكّد من ظهوره. - الإطار المضمَّن (iframe) يحوي مستنداً منفصلاً، وجذر الظل (Shadow Root) يخفي الشجرة الداخلية لمكوّن عن الاستعلامات العادية، وكلاهما يحتاج المعالجة الخاصة الموضحة في دليل استراتيجيات المحددات.
مثال تطبيقي: ثلاثة فحوص على صفحة السلة
يؤتمت مختبِر نموذج السلة من هذا الدرس، ومعه جدول سجل الطلبات. والمتطلب: بعد أن يغيّر المستخدم الكمية وينقر «Update» تظهر الرسالة «Cart updated». ثلاثة فحوص تعطي نتائج مفاجئة، وإعادة كلٍّ منها في أدوات المطور تُظهر كيف يفسّره DOM.
- الفحص الأول يفشل: بعد تغيير الكمية إلى 3 يقرأ الاختبار السمة
valueلحقل الإدخال فيحصل على "1". أعِد الفحص في لوحة Console بعد تحديد الحقل في لوحة Elements: يُعيد$0.getAttribute('value')القيمة "1"، بينما يُعيد$0.valueالقيمة "3". التطبيق سليم، لكن الاختبار قرأ السمة بدلاً من الخاصية. الإصلاح: اجعل الفحص يقرأ الخاصيةvalue. وأدوات الأتمتة توفّر زوج السمة والخاصية نفسه بأسمائها الخاصة، ويبيّن درسا Playwright وSelenium أيّ استدعاء يقرأ كلاً منهما. - الفحص الثاني يفشل: التعبير
//table[@id='orders']/trالمنسوخ من مصدر الصفحة لا يجد أي صف. وفي لوحة Console تنفّذ الدالة$x()تعبير XPath وتُعيد العناصر المطابقة: فيُعيد$x("//table[@id='orders']/tr")قائمة فارغة[]، ولوحة Elements تُظهر عنصرtbodyالذي أدرجه المحلّل. الإصلاح: حدّد الصفوف كما هي في DOM، فالاستدعاء$x("//table[@id='orders']/tbody/tr")يُعيد كل الصفوف. - الفحص الثالث ينجح، ولا ينبغي له ذلك: يقرأ الاختبار
textContentللعنصر.msgفيجد "Cart updated: 3 items". لكن المختبِر الذي يشاهد التشغيل لا يرى أي رسالة. وفي لوحة Elements ما زال العنصرpيحملhidden، وبعد تحديده يُعيد$0.hiddenفي لوحة Console القيمةtrue: غيّر السكربت النص ولم يُظهره قط. هذا عيب حقيقي، وقد فات الاختبارَ لأنه فحص النص لا الظهور. - ولترى بالضبط ما غيّره سكربت، انسخ outerHTML للنموذج قبل النقر على «Update» وبعده (انقر بالزر الأيمن على العنصر في لوحة Elements واختر Copy ثم Copy outerHTML) وقارن النسختين في أداة Text Diff. وكما في لوحة Elements، يعرض outerHTML السمات لا القيم المكتوبة في الحقول.
أخطاء شائعة
- اعتبار «عرض مصدر الصفحة» هو DOM، وكتابة المحددات أو تقارير الأخطاء بناءً عليه.
- التحقق مما كتبه المستخدم بقراءة السمة
valueأو لوحة Elements؛ فالقيمة الحالية هي الخاصيةvalue. - افتراض أن العنصر الذي يجده الاستعلام مرئي للمستخدم؛ فالوجود والظهور فحصان منفصلان.
- فهم
disabled="false"أوhidden="false"على أنه مفعَّل أو ظاهر؛ ففي هاتين السمتين العبرة بالوجود، أياً كانت القيمة. - الاحتفاظ بمرجع لعنصر عبر إعادة العرض (Re-render) واستخدامه لاحقاً، فينتهي الأمر بخطأ stale element.
- استخدام محدِّد من «Copy XPath» أو من المصدر دون التحقق في DOM الحي من أنه يطابق عنصراً واحداً بالضبط.
من منظور ضمان الجودة
في الفريق، DOM أرض مشتركة بين المختبِرين والمطورين. وعندما تبلّغ عن عيب في الواجهة، أضف ما أظهره DOM: العنصر (محدِّداً قصيراً أو outerHTML الخاص به)، وقيم السمات أو الخصائص المهمة، وهل كان العنصر مفقوداً أم مخفياً أم مكرراً. فعبارة «بعد Update يحتفظ p.msg بالسمة hidden» تُصلَح في دقائق، أما «الرسالة لا تظهر» فتُرسل المطور في رحلة بحث.
وعندما تؤتمت، اطلب من المطورين مبكراً نقاط ربط مستقرة، مثل معرّفات ذات معنى وأسماء ميسَّرة أو سمة متفق عليها لمعرّف الاختبار، واسأل أي أجزاء الصفحة يُعاد عرضها عند تغيّر البيانات. وقبل أن تبلّغ عن اختبار واجهة فاشل على أنه عيب في المنتج، انظر إلى DOM في الحالة التي فشل فيها: فكثير من حالات الفشل مشكلات في الاختبار، كعنصر قديم (Stale) أو محدِّد منسوخ من المصدر أو فحص جرى قبل وصول المحتوى، وDOM يخبرك أيّها هي.
تمرين عملي
اقرأ DOM لنموذج اشتراك
فيما يلي جزء من صفحة اشتراك في نشرة بريدية. والمتطلب: يبقى زر «Join» معطَّلاً حتى تُقبل الشروط، وبعد النقر عليه تظهر الرسالة «Check your inbox». يكتب المستخدم ana@example.com ويحدّد مربع الاختيار. والسكربت في النهاية يعمل في كل مرة يتغيّر فيها مربع الاختيار، فيضبط الخاصية disabled للزر على عكس الخاصية checked لمربع الاختيار (فالرمز ! يعني «ليس»).
أجب عن الخطوات على الورق أولاً، ثم تحقّق من إجاباتك: احفظ الشيفرة في ملف بامتداد .html وافتحه في متصفح واستخدم لوحتي Elements وConsole.
<section id="signup">
<h2>Newsletter</h2>
<!-- shown to visitors who are not signed in -->
<label for="email">Email</label>
<input id="email" type="email" value="">
<label><input id="terms" type="checkbox"> I accept the terms</label>
<button id="join" disabled>Join</button>
<p id="status" hidden></p>
</section>
<script>
const terms = document.querySelector('#terms');
const join = document.querySelector('#join');
terms.addEventListener('change', () => { join.disabled = !terms.checked; });
</script>- ارسم شجرة DOM للعنصر
section: عُقد العناصر والعُقد النصية وعقدة التعليق. واترك العُقد النصية التي لا تحوي إلا مسافات. - بعد ما فعله المستخدم، اذكر السمة
valueوالخاصيةvalueلحقل البريد، والسمةcheckedوالخاصيةcheckedلمربع الاختيار. - ماذا يحدث لسمة
disabledفي الزر عند تحديد المربع، وماذا تعرض لوحة Elements؟ - أيّ هذه المحددات يطابق عنصراً واحداً بالضبط:
#signup label،#signup input،#terms،label[for=email]؟ - اختبار زميلك يجد
#statusبعد النقر على «Join» وينجح لأن العنصر موجود. ما الخطأ في هذا الفحص، وماذا سيجد فحص أفضل؟
إظهار الإجابة النموذجية
الشجرة: للعنصر section#signup ستة أبناء من العناصر بالترتيب: h2 وlabel وinput#email وlabel وbutton#join وp#status، وعقدة التعليق بين h2 وlabel الأول. يحوي h2 العقدة النصية «Newsletter»، ويحوي label الأول «Email»، وللعنصر label الثاني ابنان: مربع الاختيار input#terms والعقدة النصية « I accept the terms»، ويحوي button النص «Join»، أما p#status فلا أبناء له. والسمات مثل for وtype وdisabled وhidden محفوظة على عناصرها، لا في الشجرة.
حقل البريد: السمة value ما زالت نصاً فارغاً كما كُتبت، والخاصية value هي "ana@example.com". مربع الاختيار: لا توجد سمة checked (فالاستدعاء getAttribute('checked') يُعيد null)، بينما قيمة الخاصية checked هي true.
يضبط السكربت الخاصية disabled على false، ولأن disabled سمة منعكسة يحذف المتصفح السمة، فتعرض لوحة Elements الوسم <button id="join">Join</button>. وإلغاء تحديد المربع يُعيد disabled.
المحددان #terms وlabel[for=email] يطابق كلٌّ منهما عنصراً واحداً. أما #signup label فيطابق تسميتين، و#signup input يطابق عنصري إدخال: حقل البريد ومربع الاختيار.
الفقرة موجودة في DOM منذ البداية، مخفية وفارغة، فعبارة «العنصر موجود» صحيحة قبل النقر وبعده ولا تثبت شيئاً. والفحص الأفضل يتأكد أن #status ظاهر ويحوي «Check your inbox». وفي هذه الصفحة سيفشل: فلا سكربت يملأ الرسالة أو يُظهرها، وهذا عيب يُبلَّغ عنه مع العنصر وسمته hidden دليلاً.
أسئلة شائعة
هل DOM هو نفسه HTML؟
لا. HTML هو النص الذي يرسله الخادم، وDOM هو الشجرة التي يبنيها المتصفح منه ويواصل تغييرها. فالمحلِّل يكمل الشيفرة ويصحّحها، والسكربتات تضيف العُقد وتحذفها وتغيّرها، فقد يختلف الاثنان كثيراً بعد تحميل الصفحة. وأمر عرض مصدر الصفحة يُظهر HTML، ولوحة Elements تُظهر DOM.
هل يحتاج المختبِر إلى تعلّم واجهة DOM البرمجية؟
تحتاج إلى قراءة DOM لا برمجته: أنواع العُقد وعلاقاتها، والفرق بين السمات والخصائص، وبضعة أوامر في Console مثل $0 وdocument.querySelectorAll للتحقق من قيمة أو محدِّد. والأتمتة تستخدم قدراً أكبر من الواجهة، لكن محدداتها وتأكيداتها تقوم على الأفكار نفسها.
لماذا لا يجد اختباري عنصراً أراه على الشاشة؟
غالباً لأن العنصر داخل إطار مضمَّن (iframe) وهو مستند منفصل، أو داخل جذر ظل (Shadow Root)، أو لم يكن قد أُضيف بعد حين بحث الاختبار، أو لأن المحدِّد كُتب من مصدر الصفحة لا من DOM الحي. وبعض ما يظهر على الشاشة ليس عناصر أصلاً: فالنص المرسوم على عنصر canvas مثلاً لا يجده أي محدِّد في DOM.
المراجع
- HTML Living Standardلغة HTML، بما فيها مراجع المحارف المسماة (الكيانات) وقواعد التحليل التي تتبعها المتصفحات.
- WebDriver (W3C)بروتوكول W3C الذي تنفّذه المتصفحات لتتحكم فيها أدوات الأتمتة مثل Selenium عن بُعد (الإصدار Level 2 مسودة عمل).
- Document Object Model (DOM) — MDNنموذج كائن المستند (DOM): شجرة العُقد التي يبنيها المتصفح من الصفحة، والتي تقرؤها السكربتات — وأدوات اختبار الواجهات — وتعدّلها.
- Locators — Playwrightكيف يعثر Playwright على العناصر، ولماذا تُعاد تحديد المحدِّدات وتنتظر تلقائياً، ولماذا يُوصى بالمحدِّدات المبنية على الدور أولاً.
تدرّب بأدوات متقن
تجميل HTML بمسافات بادئة صحيحة للعناصر المتداخلة، مع الحفاظ على كتل pre وscript وstyle.
قارن نصّين سطراً بسطر أو كلمةً بكلمة مع إبراز الاختلافات.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.