أداة Cypress

Cypress أداة مفتوحة المصدر للاختبار الشامل (End-to-End) لتطبيقات الويب. تكتب الاختبارات بـ JavaScript أو TypeScript، ويشغّلها Cypress داخل متصفح حقيقي بجوار التطبيق قيد الاختبار. مثلاً، cy.get("[data-cy=cart-count]").should("have.text", "3") يجد شارة السلة ويواصل فحصها حتى تعرض ⁦3⁩ أو تنتهي المهلة.

لماذا يهم

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

بالنسبة إلى المنتج، تكشف حزمة Cypress تعمل مع كل طلب سحب (Pull Request) حالات الانحدار في المسارات المهمة قبل أن يصادفها المستخدمون. وبالنسبة إليك، فإن توسيع حزمة Cypress قائمة مهارة عملية في أي فريق تُبنى واجهته الأمامية بـ JavaScript.

كيف يعمل Cypress: داخل المتصفح، عبر طابور أوامر

يرسل Selenium (بلغات Java وPython وC# وJavaScript وRuby) أوامر W3C WebDriver عبر HTTP إلى مشغّل مثل chromedriver أو safaridriver، وتكتب أنت الانتظار الصريح وتنتقل بين النوافذ بمعرّفاتها (handles). ويقود Playwright (بلغات TypeScript/JavaScript وPython وJava و.NET) المتصفحات Chromium وFirefox وWebKit عبر اتصاله الخاص، بإجراءات تنتظر تلقائياً وتأكيدات تعيد المحاولة، ويستطيع استخدام عدة صفحات في اختبار واحد. وكلاهما يشغّل الاختبارات في عملية (process) منفصلة. أما Cypress فيشغّل ملف المواصفة (spec) داخل المتصفح، في حلقة تشغيل التطبيق نفسها، وخلفه عملية Node.js تشغّل المتصفح وتنفّذ مهام Node المعرَّفة في ملف الإعدادات، والتي يستدعيها الاختبار بـ cy.task(). ويتحكم Cypress أيضاً في الشبكة، وهذا ما يجعل cy.intercept() ممكناً.

يقود Selenium وPlaywright المتصفح من عملية اختبار منفصلة، أما Cypress فيشغّل ملف المواصفة داخل المتصفح بجوار التطبيق، مع تحكم في الشبكة، وعلامة تبويب واحدة لكل اختبار، وJavaScript فقط.

Selenium WebDriver

شيفرة الاختبار خارج المتصفح

  1. 1عملية الاختبار لديكأوامر W3C WebDriver عبر HTTP
  2. 2مشغّل المتصفحchromedriver, safaridriver
  3. 3المتصفح
لغات الاختبار:
Java, Python, C#, JavaScript, Ruby
الانتظار:
انتظار صريح تكتبه أنت
علامات التبويب في الاختبار الواحد:
عدة نوافذ
المتصفحات المذكورة في هذا الدرس:
Chrome وSafari وغيرهما، كلٌّ عبر مشغّله

Playwright

شيفرة الاختبار خارج المتصفح

  1. 1عملية الاختبار لديكاتصال Playwright الخاص
  2. 2المتصفحChromium, Firefox, WebKit
لغات الاختبار:
TypeScript/JavaScript, Python, Java, .NET
الانتظار:
إجراءات تنتظر تلقائياً وتأكيدات تعيد المحاولة
علامات التبويب في الاختبار الواحد:
عدة صفحات وسياقات متصفح
المتصفحات المذكورة في هذا الدرس:
Chromium, Firefox, WebKit

Cypressهذا الدرس

شيفرة الاختبار داخل المتصفح

  1. 1عملية Node.js في Cypressتشغّل المتصفح وتنفّذ المهام التي يستدعيها الاختبار بـ cy.task()
  2. 2المتصفح: ملف المواصفة بجوار التطبيقحلقة التشغيل نفسها، والشبكة تحت تحكم Cypress
لغات الاختبار:
JavaScript, TypeScript
الانتظار:
الاستعلامات والتأكيدات يُعاد تنفيذها حتى defaultCommandTimeout
علامات التبويب في الاختبار الواحد:
علامة تبويب واحدة؛ وإضافة لعلامة ثانية
المتصفحات المذكورة في هذا الدرس:
Chrome, Edge, Firefox

توضع الأوامر في طابور ولا تُنفَّذ لحظة تنفيذ سطرها: تنضم cy.visit() وcy.get() و.click() إلى طابور يشغّله Cypress بعد انتهاء دالة الاختبار. وهي ليست وعوداً (Promises)، فلا يمكنك انتظارها بـ await ولا حفظ نتيجتها؛ أضف .then() أو .should() إلى السلسلة بدلاً من ذلك.

والفكرة المحورية هي قابلية إعادة المحاولة (Retry-ability): يُعاد تنفيذ الاستعلامات مثل cy.get() وcy.contains() مع التأكيدات المتصلة بها حتى تنجح التأكيدات أو تنتهي مهلة defaultCommandTimeout (⁦4⁩ ثوانٍ افتراضياً). أما الإجراءات مثل .click() فتُنفَّذ مرة واحدة بعد أن تنتظر حتى يصبح العنصر قابلاً للتفاعل: ظاهراً ومفعّلاً وغير مغطّى. أكّد الحالة التي تتوقعها بدلاً من إضافة cy.wait(5000).

cy.visit('/cart')
cy.get('[data-cy=add-item]').click()   // action: waits until actionable, runs once
cy.get('[data-cy=cart-count]')         // query: retried...
  .should('have.text', '1')            // ...until this passes or the timeout expires

// Wrong: commands are queued, they return no value you can store
// const count = cy.get('[data-cy=cart-count]').text()

// Right: work with the yielded value inside .then()
cy.get('[data-cy=cart-count]').invoke('text').then((text) => {
  cy.log(`The badge shows ${text}`)
})

التثبيت وبنية المشروع وملف الإعدادات

ثبّت Cypress في كل مشروع بالأمر npm install --save-dev cypress؛ وأول تشغيل لـ npx cypress open يفتح شاشة Launchpad، واختيار E2E Testing فيها ينشئ ملف الإعدادات ومجلد cypress/. ويستخدم ملف الإعدادات أدناه صيغة CommonJS في Node، أي require() وmodule.exports، وهي المقابل الأقدم لـimport وexport، لأن Node هو الذي يحمّله؛ أما ملفات المواصفات وملفات الدعم (support) وكائنات الصفحات فيمكنها استخدام import وexport، لأن Cypress يحزّمها (bundle) قبل تشغيلها في المتصفح.

في ملف الإعدادات، يوجّه baseUrl الأمرَ cy.visit("/orders") إلى بيئة معيّنة؛ وتحدّ المهلات مدة الاستعلامات وتحميل الصفحات والاستجابات؛ ويُضبط retries بقيمة مستقلة لكل من cypress run وcypress open؛ ويختار reporter صيغة المخرجات، مثل JUnit XML لخادم CI. ويُختار المتصفح عند التشغيل بـ --browser chrome أو edge أو firefox؛ ويحدد defaultBrowser المتصفح المستخدم دون هذا الخيار (وإلا استخدم cypress run متصفح Electron المضمَّن، وقد صار مُهمَلاً (deprecated) منذ Cypress 16)، ويمكن تصفية قائمة browsers داخل setupNodeEvents.

  • cypress/e2e/: ملفات المواصفات مثل orders.cy.js.
  • cypress/fixtures/: بيانات الاستجابات البديلة مثل orders.json، والملفات المراد رفعها.
  • cypress/support/: يعمل e2e.js قبل كل مواصفة ويستورد commands.js الذي يضم الأوامر المخصصة مثل cy.login()؛ وكثيراً ما توضع كائنات الصفحات بجواره.
  • cypress/screenshots/ وcypress/downloads/: مخرجات التشغيل، وتبقى خارج نظام التحكم في الإصدارات.
// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',        // cy.visit('/orders') opens this host
    specPattern: 'cypress/e2e/**/*.cy.js',
  },
  defaultBrowser: 'chrome',                  // used when --browser is not given
  defaultCommandTimeout: 6000,               // queries and assertions (default 4000)
  pageLoadTimeout: 60000,                    // page loads after cy.visit()
  responseTimeout: 30000,                    // cy.request() and cy.wait() responses
  retries: { runMode: 1, openMode: 0 },      // one rerun in cypress run only
  reporter: 'junit',
  reporterOptions: { mochaFile: 'results/junit-[hash].xml' },
  screenshotOnRunFailure: true,
  video: false,
})

جرّبها: منسّق JavaScript

شغّل اختباراً واحداً، وصحّحه، وشغّل الحزمة في CI

أثناء الكتابة استخدم npx cypress open: اختر متصفحاً وملف مواصفة، فيُعاد التشغيل مع كل حفظ. والنقر على أمر في سجل الأوامر (Command Log) يعرض الصفحة كما كانت في تلك اللحظة، وهو «السفر عبر الزمن» (time travel). ومن سطر الأوامر وفي CI:

  • ملف مواصفة واحد: npx cypress run --spec cypress/e2e/orders.cy.js --browser chrome.
  • اختبار واحد: it.only( أثناء العمل، واحذفه قبل الإيداع (commit).
  • الوضع المرئي (headed): يخفي cypress run المتصفح؛ أضف --headed لتشاهد التشغيل.
  • التصحيح: يوقف cy.pause() الطابور في المشغّل التفاعلي، ويتوقف .debug() عند نقطة توقف (breakpoint) إذا كانت أدوات المطوّر (DevTools) مفتوحة، ويطبع DEBUG=cypress:* (صيغة Linux وmacOS) سجلات Cypress الداخلية حين لا يبدأ أو لا يشغّل المتصفح.
  • CI: تحتوي الصورة الرسمية cypress/browsers على Node.js ومتصفحات (وتضيف cypress/included إصداراً ثابتاً من Cypress). تشغّل المهمة أدناه كل المواصفات في Chrome، وتأخذ كلمة المرور من أسرار المستودع، وتحفظ نتائج JUnit ولقطات الفشل.
# .github/workflows/e2e.yml
name: e2e
on: [pull_request]

jobs:
  cypress:
    runs-on: ubuntu-latest
    container:
      image: cypress/browsers:latest     # pin an exact tag in a real pipeline
    steps:
      - uses: actions/checkout@v7
      - run: npm ci
      - run: npx cypress run --browser chrome --config baseUrl=https://test.example.com
        env:
          CYPRESS_BUYER_PASSWORD: ${{ secrets.BUYER_PASSWORD }}
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: cypress-results
          path: |
            results
            cypress/screenshots

جرّبها: منسّق YAML

الجلسات واستجابات الشبكة البديلة ونوافذ الحوار وعلامات التبويب ورفع الملفات

عزل الاختبارات مفعّل افتراضياً: يبدأ كل اختبار بصفحة فارغة دون ملفات تعريف الارتباط (cookies) أو التخزين، لذا يجب أن يسجّل كل اختبار الدخول. يشغّل cy.session() الدخول مرة واحدة، ويحفظ ملفات تعريف الارتباط والتخزين الناتجة، ويستعيدها للاختبارات اللاحقة التي تستخدم المعرّف نفسه؛ وتتحقق validate من أن الجلسة المستعادة ما زالت صالحة. وزُر صفحة بعد ذلك، لأن الصفحة تكون فارغة.

يطابق cy.intercept() الطلبات بالطريقة (method) والعنوان. ومع استجابة ثابتة مثل { fixture: "orders.json" } أو { statusCode: 500 } يستبدل الخادم باستجابة بديلة (stub)، فتصل إلى حالات القائمة الفارغة والأخطاء متى شئت؛ ومن دونها يراقب فقط، وينتظر cy.wait("@alias") الطلب لتتحقق منه.

  • نوافذ الحوار: يقبل Cypress alert() وconfirm() تلقائياً. مرِّر إلى cy.on("window:alert", …) دالة بديلة (stub)، أي دالة تحلّ محل الأصلية وتسجّل استدعاءاتها، ثم أكّد على اسمها المستعار، فيفشل الاختبار أيضاً إذا لم يظهر التنبيه؛ والدالة البديلة لـ window:confirm التي تعيد false تختار «Cancel».
  • علامات التبويب الجديدة: يقود Cypress علامة تبويب واحدة. للرابط الذي يحمل target="_blank" تحقّق من href وtarget، ثم زُر العنوان في علامة التبويب نفسها أو احذف target قبل النقر؛ ويشير التوثيق إلى الإضافة @cypress/puppeteer حين لا مفرّ من علامة تبويب ثانية.
  • رفع الملفات: .selectFile("cypress/fixtures/receipt.pdf") على حقل الملف؛ وأضف { action: "drag-drop" } لمنطقة الإفلات.
// cypress/support/commands.js: sign in once per user, reuse the session
Cypress.Commands.add('login', (email) => {
  cy.session(email, () => {
    cy.env(['BUYER_PASSWORD']).then(({ BUYER_PASSWORD }) => {
      cy.request({
        method: 'POST',
        url: '/api/login',
        body: { email, password: BUYER_PASSWORD },
        log: false,                          // keep the password out of the log
      })
    })
  }, {
    validate() {
      cy.request('/api/me').its('status').should('eq', 200)
    },
  })
})

// In a spec: watch a request, check an alert, upload a file
cy.login('buyer@example.com')
cy.intercept('POST', '/api/receipts').as('upload')     // watch only, no stub
cy.on('window:alert', cy.stub().as('alert'))           // record every alert
cy.visit('/account/receipts')
cy.get('[data-cy=receipt-file]').selectFile('cypress/fixtures/receipt.pdf')
cy.get('[data-cy=upload]').click()
cy.wait('@upload').its('response.statusCode').should('eq', 201)
cy.get('@alert').should('have.been.calledOnceWith', 'Receipt uploaded')

جرّبها: مولّد استجابات API وهمية جرّبها: مولّد استجابات خطأ وهمية

مثال تطبيقي: سجل الطلبات في أربعة اختبارات مع كائن صفحة

كثيراً ما يسجّل أول اختبار Cypress الدخول ويفحص القائمة ويلغي طلباً ويفحص رسالة خطأ، كل ذلك في it واحدة. وعندما تفشل خطوة مبكرة لا تُنفَّذ الفحوص اللاحقة. قسّمه حسب السلوك.

المتطلب: «تعرض صفحة سجل الطلبات طلبات المشتري. وإذا لم توجد طلبات تعرض You have no orders yet؛ وإذا فشل التحميل تعرض Could not load your orders. ويطلب الإلغاء تأكيداً، واختيار Cancel يُبقي الطلب».

كائن الصفحة يضم المحددات ولا يحتوي على تأكيدات. ويضم ملف البيانات orders.json ثلاثة طلبات، حالة الطلب A-1002 منها Processing. وكل اختبار يستبدل بياناته بنفسه، ويعيد استخدام الجلسة المحفوظة، ويفحص سلوكاً واحداً، فيسمّي عنوانُ الاختبار الفاشل ما انكسر.

// cypress/support/pages/ordersPage.js: knows the screen, makes no assertions
export const ordersPage = {
  visit: () => cy.visit('/account/orders'),
  rows: () => cy.get('[data-cy=order-row]'),
  row: (id) => cy.get(`[data-cy=order-row][data-order-id="${id}"]`),
  message: () => cy.get('[data-cy=orders-message]'),
  cancel: (id) => ordersPage.row(id).find('[data-cy=cancel-order]').click(),
}

// cypress/e2e/orders.cy.js: one behaviour per test
import { ordersPage } from '../support/pages/ordersPage'

describe('Order history', () => {
  beforeEach(() => {
    cy.login('buyer@example.com')
  })

  it('lists the orders returned by the API', () => {
    cy.intercept('GET', '/api/orders', { fixture: 'orders.json' }).as('orders')
    ordersPage.visit()
    cy.wait('@orders')
    ordersPage.rows().should('have.length', 3)
  })

  it('shows the empty state when there are no orders', () => {
    cy.intercept('GET', '/api/orders', { body: [] })
    ordersPage.visit()
    ordersPage.message().should('have.text', 'You have no orders yet')
  })

  it('shows an error when the orders cannot be loaded', () => {
    cy.intercept('GET', '/api/orders', { statusCode: 500, body: {} })
    ordersPage.visit()
    ordersPage.message().should('have.text', 'Could not load your orders')
  })

  it('keeps the order when the buyer cancels the confirmation', () => {
    cy.intercept('GET', '/api/orders', { fixture: 'orders.json' })
    cy.on('window:confirm', cy.stub().as('confirm').returns(false))  // choose Cancel
    ordersPage.visit()
    ordersPage.cancel('A-1002')
    cy.get('@confirm').should('have.been.calledOnceWith', 'Cancel order A-1002?')
    ordersPage.row('A-1002').should('contain', 'Processing')
  })
})

جرّبها: مدقّق JSON جرّبها: مولّد استجابات API وهمية

أخطاء شائعة

  • إضافة cy.wait(5000) بدلاً من تأكيد الحالة المتوقعة أو انتظار طلب له اسم مستعار (alias).
  • معاملة الأوامر كشيفرة JavaScript عادية: حفظ cy.get() في متغير، أو استخدام await، أو التفرّع بـ if على حالة لم يصل إليها الطابور بعد.
  • تسجيل الدخول عبر الواجهة في كل اختبار بدلاً من استخدام cy.session().
  • استبدال كل الطلبات، فتبقى الحزمة خضراء بينما الواجهة الأمامية والواجهة البرمجية غير متوافقتين.
  • استخدام retries لإخفاء الاختبارات المتذبذبة (Flaky)؛ فإعادة المحاولة احتواء لا إصلاح.
  • إيداع it.only الذي يتخطى بقية المواصفة بصمت، أو كلمات مرور حقيقية بدلاً من أسرار CI.

من منظور ضمان الجودة

يتعامل المختبِر مع Cypress حين يختار المسارات التي تستحق اختباراً شاملاً (قليل منها وذو قيمة، والباقي في مستويات أدنى من الهرم)، وحين يكتب المواصفات ويراجعها مع المطورين، وحين يقرأ حالات الفشل في CI. واتفق مبكراً على سمات data-cy كما يوصي دليل أفضل الممارسات في Cypress.

عندما يفشل اختبار في CI، ابدأ من رسالة الخطأ ولقطة الفشل، وقرّر هل هو عيب في المنتج أم في الاختبار أم مشكلة في البيئة قبل إعادة التشغيل. ويذكر تقرير الخطأ ملف المواصفة والاختبار والمتصفح وإصدار Cypress والبناء (build)، وهل كانت البيانات بديلة.

تمرين عملي

خطّط مواصفة Cypress لحقل القسيمة

المطلوب

إجمالي السلة ⁦200.00⁩ ريال قبل أي قسيمة. المتطلب: «الرمز الصالح يطبّق خصماً بنسبة ⁦10%⁩ ويعرض Coupon applied. والرمز المنتهي يعرض This coupon has expired. وإذا تعطلت خدمة القسائم تعرض الصفحة Coupons are unavailable, try again later ولا يتغير الإجمالي». وترسل الصفحة الطلب الموضح أدناه.

صمّم كائن صفحة للسلة واختباراً واحداً لكل سلوك، يستبدل كل منها استجابته بـ cy.intercept(). تكفي الورقة.

المادة
POST /api/coupons/apply
Content-Type: application/json

{"code": "SAVE10"}

Possible responses
200 OK                     {"code": "SAVE10", "discountPercent": 10, "total": 180.00}
422 Unprocessable Content  {"error": "COUPON_EXPIRED"}
503 Service Unavailable    {"error": "SERVICE_UNAVAILABLE"}
الخطوات
  1. اكتب السلوكيات والاستجابة التي يحتاجها كل منها.
  2. اكتب كائن الصفحة: محددات وإجراءات، بلا تأكيدات.
  3. اكتب it لكل سلوك مع الاستجابة البديلة والتأكيدات، بما فيها الإجمالي.
  4. دوّن ما لا تثبته الاستجابات البديلة وكيف ستغطيه.
إظهار الإجابة النموذجية
الإجابة النموذجية

السلوكيات: رمز صالح (⁦200⁩، total: 180.00)، ورمز منتهٍ (⁦422⁩)، وتعطل الخدمة (⁦503⁩). والرمز الفارغ لا يحتاج إلى استبدال: يبقى زر Apply معطلاً أو تظهر رسالة تحقق، بحسب اتفاق الفريق.

كائن الصفحة: visit() وapply(code) (يكتب في [data-cy=coupon-code] وينقر [data-cy=apply-coupon]) وmessage() وtotal()؛ ولا يحتوي على should().

الاختبارات: (⁦1⁩) استبدل استجابة ⁦200⁩ باسم @apply، وطبّق SAVE10، وتحقق أن cy.wait("@apply").its("request.body") يساوي { code: "SAVE10" }، وأن الرسالة Coupon applied والإجمالي ⁦180.00⁩. (⁦2⁩) استبدل استجابة ⁦422⁩، وتوقّع This coupon has expired و⁦200.00⁩. (⁦3⁩) استبدل استجابة ⁦503⁩، وتوقّع Coupons are unavailable, try again later و⁦200.00⁩.

لا تثبت الاستجابات البديلة أن الخدمة الحقيقية تقبل SAVE10 وتحسب ⁦10%⁩ بشكل صحيح؛ فأضف اختبار API واختباراً شاملاً واحداً ببيانات قسائم مُهيّأة مسبقاً. واسأل مالك المنتج: هل يُطبَّق الخصم قبل ضريبة القيمة المضافة والتوصيل، وهل يمكن الجمع بين القسائم؟

أسئلة شائعة

هل أتعلّم Cypress أم Playwright؟

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

لماذا لا أستطيع استخدام async/await مع أوامر Cypress؟

لأن الأوامر توضع في طابور ولا تُنفَّذ عند تشغيل سطرها، وليست وعوداً (Promises). يشغّل Cypress الطابور بعد انتهاء دالة الاختبار، ويعيد محاولة الاستعلامات أثناء ذلك. استخدم .then() للتعامل مع القيمة الناتجة، والأسماء المستعارة لإعادة استخدامها، و.should() لتأكيدها مع إعادة المحاولة.

هل يختبر Cypress الواجهات البرمجية (API)؟

يرسل cy.request() طلبات HTTP من داخل الاختبار. وهو مفيد للتجهيز، مثل إنشاء البيانات أو تسجيل الدخول، ولبضعة فحوص API بجوار اختبارات الواجهة. لكن Cypress يحتاج إلى متصفح ليعمل، لذا تكون حزمة API الكبيرة عادةً أبسط في أداة أو مكتبة مخصصة لاختبار API.

هل أفعّل إعادة المحاولة (retries)؟

محاولة أو محاولتان في cypress run قد تُبقيان خط CI قابلاً للاستخدام ريثما يُصلَح اختبار متذبذب، لكن كل نجاح بعد إعادة محاولة يجب أن يظهر في التقرير ويُعامل كعيب في الاختبار أو المنتج. وأبقِ قيمة openMode عند ⁦0⁩ لترى التذبذب أثناء كتابة الاختبارات.

المراجع

  • Cypress DocumentationCypress.ioالتوثيق الرسمي لاختبار الويب الشامل واختبار المكونات في المتصفح باستخدام Cypress.
  • Best Practices — CypressCypress.ioإرشادات Cypress لاختيار العناصر بسمات مخصّصة، وتجنّب الانتظار الثابت، وإبقاء الاختبارات مستقلة.
  • GitHub Actions documentationGitHubكيف تُعرَّف مسارات العمل والمهام والمشغّلات والمخرجات في GitHub Actions — حيث تعمل الاختبارات الآلية عند كل دفع أو طلب دمج.

تدرّب بأدوات متقن

ولّد استجابات API وهمية كاملة (الحالة والترويسات وجسم JSON) لحالات النجاح والقوائم والتفاصيل والأخطاء والنتائج الفارغة.

المحاكاة

ولّد استجابات خطأ منظّمة (التحقق والمصادقة وعدم العثور وتحديد المعدّل وخطأ الخادم) بالصيغ الشائعة.

المحاكاة

التحقق من صحة صياغة JSON مع تحديد مواقع الأخطاء بدقة واقتراحات مفيدة للإصلاح.

JSON والبيانات

تنسيق YAML والتحقق من صحته بمسافات بادئة وعلامات اقتباس متسقة.

التنسيق

تجميل كود JavaScript وTypeScript بمسافات بادئة وتباعد متسقين.

التنسيق

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة