تست end-to-end با استفاده ازReact ، Jest و TestProject JavaScript OpenSDK

آفلاین
user-avatar
عرفان حشمتی
06 مهر 1400, خواندن در 10 دقیقه

با داشتن یک لیست طولانی از فریمورک‌های تست end-to-end (e2e)، تشخیص اینکه از کدام یک باید استفاده کنید کمی دشوار است. Cypress و Selenium به عنوان پرکاربردترین گزینه‌ها بازار را در دست دارند. همچنین Appium برای تست برنامه‌های تلفن همراه، Puppeteer برای خودکارسازی فرایندها در کروم و Protractor برای برنامه‌های انگولار اغلب مورد استفاده قرار می‌گیرند.

به تازگی هم یک فریمورک تازه کار وارد این حوزه شده به نام TestProject، یک پلتفرم رایگان و متن باز برای تست‌های خودکار e2e که به ساده‌سازی تست وب، تلفن همراه و API کمک می‌کند. TestProject SDK دارای پشتیبانی زبان از جاوا، سی شارپ، پایتون و جاوااسکریپت است.

در این مقاله قصد داریم نشان دهیم که چگونه می‌توان از TestProject JavaScript OpenSDK برای تست یک برنامه ری‌اکت با Jest به عنوان فریمورک تست استفاده کرد.

مرور کلی برنامه

برای شروع بیایید نگاهی به دموی برنامه‌ای داشته باشیم که در حال تست آن هستیم. این برنامه نسبتا ساده است، فقط یک فرم درخواست ساده که در آن کاربر می‌تواند نام، نام خانوادگی و آدرس ایمیل خود را وارد کند، وجود دارد.

دموی برنامه: فرم درخواست

اگر فرم بدون پر کردن ورودی‌ها ارسال شود، پیامهای خطا در زیر هر فیلد نامعتبر نشان داده می‌شود.

دموی برنامه: ورودی نامعتبر

پس از ارسال فرم به صورت موفقیت آمیز، برنامه یک متن تأیید نشان می‌دهد.

دموی برنامه: پر کردن فرم

 

دموی برنامه: صفحه تأیید

 

به اندازه کافی ساده بود، درست است؟ اگر می‌خواهید نسخه دمو را در حالت اجرا ببینید، می‌توانید آن را در این لینک پیدا کنید یا کد منبع آن را در گیت هاب مشاهده نمایید.

اکنون بیایید به نحوه ساخت برنامه بپردازیم.

ایجاد برنامه React

همانطور که در بالا ذکر شد، این برنامه در ری‌اکت نوشته شده است. به منظور ساده سازی کد، از ابزار create-react-app برای راه اندازی برنامه استفاده کرده‌ایم.

npx create-react-app testproject-demo

پس از ایجاد ساختار کلی برنامه، محتوای پیش فرض آن را حذف کردیم و یک کامپوننت فرم ساده را در فایلی به نام RequestForm.js نوشتیم. در اینجا کد فرم درخواست به طور کامل آورده شده است:

import React, { useState } from 'react'
import './RequestForm.css'
 
export const RequestForm = () => {
  const [firstName, setFirstName] = useState('')
  const [lastName, setLastName] = useState('')
  const [email, setEmail] = useState('')
 
  const handleFirstNameChange = e => {
    setFirstName(e.target.value)
  }
 
  const handleLastNameChange = e => {
    setLastName(e.target.value)
  }
 
  const handleEmailChange = e => {
    setEmail(e.target.value)
  }
 
  const [firstNameError, setFirstNameError] = useState('')
  const [lastNameError, setLastNameError] = useState('')
  const [emailError, setEmailError] = useState('')
 
  const [submitted, setSubmitted] = useState(false)
 
  const handleSubmit = e => {
    e.preventDefault()
 
    setFirstNameError(firstName ? '' : 'First Name field is required')
    setLastNameError(lastName ? '' : 'Last Name field is required')
    setEmailError(email ? '' : 'Email field is required')
 
    if (firstName && lastName && email) {
      setSubmitted(true)
    }
  }
 
  return submitted ? (
    <p id="submissionConfirmationText">
      Thank you! We will be in touch with you shortly.
    </p>
  ) : (
    <form className="requestForm" onSubmit={handleSubmit}>
      <div className={`formGroup${firstNameError ? ' error' : ''}`}>
        <label htmlFor="firstName">First Name</label>
        <input
          name="firstName"
          id="firstName"
          data-testid="firstName"
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </div>
      {firstNameError && (
        <p className="errorMessage" id="firstNameError">
          {firstNameError}
        </p>
      )}
      <div className={`formGroup${lastNameError ? ' error' : ''}`}>
        <label htmlFor="lastName">Last Name</label>
        <input
          name="lastName"
          id="lastName"
          data-testid="lastName"
          value={lastName}
          onChange={handleLastNameChange}
        />
      </div>
      {lastNameError && (
        <p className="errorMessage" id="lastNameError">
          {lastNameError}
        </p>
      )}
      <div className={`formGroup${emailError ? ' error' : ''}`}>
        <label htmlFor="email">Email</label>
        <input
          type="email"
          name="email"
          id="email"
          data-testid="email"
          value={email}
          onChange={handleEmailChange}
        />
      </div>
      {emailError && (
        <p className="errorMessage" id="emailError">
          {emailError}
        </p>
      )}
      <button type="submit" id="requestDemo">
        Request Demo
      </button>
    </form>
  )
}

همانطور که می‌بینید، ما یک کامپوننت تابع داریم که سه ورودی را برای نام کاربر، نام خانوادگی و آدرس ایمیل نمایش می‌دهد. دکمه ارسال "Request Demo" هم در پایین فرم وجود دارد. هنگامی که فرم ارسال می‌شود، پیام‌های خطا در صورت وجود ورودی‌های نامعتبر و پیام تأیید در صورت ارسال موفقیت آمیز فرم، نمایان می‌گردد.

این تنها چیزی است که در برنامه وجود دارد. اکنون بیایید وارد بحث اصلی شویم. چگونه می‌توان تست‌های e2e را با TestProject پیکربندی کرد؟

شروع کار با TestProject

برای شروع ابتدا باید یک حساب رایگان TestProject ایجاد کنیم. پس از آن می‌توانیم ایجنت TestProject را دانلود کنیم. البته گزینه‌هایی برای دسکتاپ یا داکر وجود دارد. اینکه کدام یک را انتخاب می‌کنید به خودتان بستگی دارد، من خودم برنامه دسکتاپ را برای سیستم‌عامل مک دانلود کردم. سپس باید ایجنت خود را ثبت کرده تا بتوانید آن را به حساب TestProject خود لینک کنید.

در مرحله بعد، یک توکن برای استفاده در پروژه خود می‌سازیم. سپس یک فایل env. در دایرکتوری اصلی پروژه خود ایجاد کرده و کد زیر را برای ذخیره رمز در متغیر محیط TP_DEV_TOKEN اضافه می‌کنیم:

TP_DEV_TOKEN=<YOUR DEV TOKEN HERE>

توجه داشته باشید که در فایل gitignore. به Git می‌گوییم که فایل env. را نادیده بگیرد تا توکن یا سایر اطلاعات محرمانه ما در کنترل نسخه ذخیره نگردد و به طور تصادفی با دیگران به اشتراک گذاشته نشود.

در نهایت باید چند پکیج npm را به عنوان وابستگی نصب کرده تا بتوانیم از TestProject JavaScript OpenSDK در برنامه خود استفاده کنیم:

yarn add --dev @tpio/javascript-opensdk selenium-webdriver

از این طریق زمینه کار را برای شروع استفاده از TestProject برای تست e2e خود آماده کرده‌ایم.

پیکربندی Jest

در مرحله بعد باید Jest را پیکربندی کنیم. از آنجا که ما از ابزار create-react-app برای راه اندازی برنامه خود استفاده کردیم، پروژه ما از اسکریپتهای react برای اجرای Jest و React Testing Library با برخی از گزینههای پیکربندی پیش فرض استفاده میکند. با این حال خوب است که بتوانیم Jest را پیکربندی کنیم و چند اسکریپت npm دیگر اضافه کنیم تا تستهای واحد و تستهای e2e را به طور جداگانه اجرا نماییم.

برای انجام این کار، اسکریپت‌های npm زیر را به بخش "scripts" فایل package.json خود اضافه می‌کنیم. هر یک شامل برخی از گزینه‌های پیکربندی خاص Jest CLI است:

"scripts": {
  ...other scripts here
  "start": "react-scripts start",
  "test:e2e": "wait-on http://localhost:3000/testproject-demo/build/ && react-scripts test --testPathPattern=\"(\\.|/)e2e\\.(test|spec)\\.[jt]sx?$\" --testTimeout=30000 --runInBand --watchAll=false",
  "test:e2e:ci": "run-p start test:e2e",
  "test:e2e:watch": "wait-on http://localhost:3000/testproject-demo/build/ && react-scripts test --testPathPattern=\"(\\.|/)e2e\\.(test|spec)\\.[jt]sx?$\" --testTimeout=30000 --runInBand",
  "test:unit": "react-scripts test --testPathPattern=\"(\\.|/)unit.(test|spec)\\.[jt]sx?$\" --watchAll=false",
  "test:unit:coverage": "react-scripts test --testPathPattern=\"(\\.|/)unit.(test|spec)\\.[jt]sx?$\" --watchAll=false --coverage",
  "test:unit:watch": "react-scripts test --testPathPattern=\"(\\.|/)unit.(test|spec)\\.[jt]sx?$\""
},

این بخش چند مورد دارد که باید آنها را توضیح دهیم. بنابراین بگذارید هر یک از این دستورات را به نوبت تجزیه کنیم.

ابتدا اسکریپت شروع را می‌بینیم که به اندازه کافی ساده است. این اسکریپت برنامه ما را به صورت محلی در حالت توسعه اجرا می‌کند و از اهمیت زیادی برخوردار است، زیرا تست e2e نیاز به یک برنامه برای عملکرد صحیح دارد.

بعد اسکریپت test: e2e را داریم. این دستور منتظر می‌ماند تا برنامه قبل از اجرای هرگونه تست روی پورت 3000 اجرا شود. سپس از دستور test react-scripts برای اجرای تست برنامه استفاده می‌کند، همچنین چندین گزینه پیکربندی Jest CLI بر روی آن اعمال شده است.

testPathPattern به Jest می‌گوید فقط تست‌هایی را که به e2e.test.js ختم می‌شود (و چند مورد دیگر) اجرا کند. testTimeout هم بازه زمانی پیش فرض 5 ثانیه در هر تست را به 30 ثانیه افزایش می‌دهد، زیرا اجرای تست‌های e2e کمی بیشتر از تست‌های واحد طول می‌کشد.

runInBand به Jest می‌گوید فایل‌های تست را به صورت موازی اجرا کند، زیرا ما فقط یک ایجنت TestProject بر روی دستگاه خود نصب کرده‌ایم.

و در نهایت watchAll = false باعث می‌شود تا تست‌ها در حالت "watch" اجرا نشوند (این تنظیمات پیش فرض Jest با react-scripts است).

اسکریپت سوم یعنی test:e2e:ci ترکیبی از دستورات start و test:e2e است که برای ساده سازی فرایند تست به کار می‌رود. برای استفاده از دستور test: e2e باید برنامه را به صورت محلی اجرا کنیم.

بنابراین باید ابتدا yarn start را انجام دهیم و سپس yarn test:e2e را اجرا کنیم. اکنون یک فرآیند ساده‌تر داریم که در آن می‌توانیم yarn test:e2e:ci را اجرا کرده و هم برنامه را راه اندازی کنیم و هم تست e2e را انجام دهیم.

چهارمین اسکریپت یعنی test: e2e: watch بسیار شبیه به اسکریپت test: e2e است، اما در صورتی که بخواهید هنگام اعمال تغییرات در برنامه خود تست‌ها به طور مداوم در پس زمینه اجرا شود، این کار را می‌کند.

سه اسکریپت آخر برای اجرای تست‌های واحد است. اسکریپت test:unit تست‌های واحد را با Jest و React Testing Library اجرا می‌کند و فقط به دنبال تست‌هایی است که به unit.test.js (و چند مورد دیگر) ختم می‌شود.

اسکریپت test:unit:coverage همان تست‌های واحد را اجرا می‌کند اما شامل گزارش تست نیز می‌شود. و سرانجام اسکریپت test: unit: watch تست‌های واحد را در حالت watch اجرا می‌کند.

این توضیحات ممکن است کمی پیچیده به نظر برسد، اما نکته مهم اینجاست که ما اکنون چندین اسکریپت مفید npm ایجاد کرده‌ایم که به ما امکان می‌دهد به راحتی تست‌های واحد و e2e خود را با دستورات کوتاه و ساده اجرا کنیم.

همه کارهای سخت پیکربندی انجام شده‌اند، بنابراین اکنون می‌توانیم روی نوشتن تست واقعی تمرکز کنیم.

نوشتن تست‌ها با JavaScript OpenSDK

ما اکنون Jest و TestProject را برای پروژه خود پیکربندی کرده‌ایم، بنابراین آماده هستیم تا اولین تست e2e خود را بنویسیم. تست‌های end-to-end معمولا بر گردش کار برنامه که توسط فعالیت‌های کاربر نشان داده می‌شود، تمرکز می‌کند.

برای این فرم درخواست، می‌توانیم دو فعالیت مهم کاربر را در نظر بگیریم: هنگامی که کاربر سعی می‌کند یک فرم نامعتبر ارسال کند و هنگامی که کاربر با موفقیت یک فرم پر شده را به درستی ارسال می‌کند. پس بیایید برای هر گردش کار یک تست e2e بنویسیم.

فایل کامل App.e2e.test.js ما به شکل زیر است:

import { By } from 'selenium-webdriver'
import { Builder } from '@tpio/javascript-opensdk'
 
describe('App', () => {
  const testUrl = 'http://localhost:3000/testproject-demo/build/'
 
  let driver
 
  beforeEach(async () => {
    driver = await new Builder()
      .forBrowser('chrome')
      .withProjectName('TestProject Demo')
      .withJobName('Request Form')
      .build()
  })
 
  afterEach(async () => {
    await driver.quit()
  })
 
  it('allows the user to submit the form when filled out properly', async () => {
    await driver.get(testUrl)
    await driver.findElement(By.css('#firstName')).sendKeys('John')
    await driver.findElement(By.css('#lastName')).sendKeys('Doe')
    await driver.findElement(By.css('#email')).sendKeys('[email protected]')
    await driver.findElement(By.css('#requestDemo')).click()
 
    await driver
      .findElement(By.css('#submissionConfirmationText'))
      .isDisplayed()
  })
 
  it('prevents the user from submitting the form when not filled out properly', async () => {
    await driver.get(testUrl)
    await driver.findElement(By.css('#requestDemo')).click()
 
    await driver.findElement(By.css('#firstNameError')).isDisplayed()
    await driver.findElement(By.css('#lastNameError')).isDisplayed()
    await driver.findElement(By.css('#emailError')).isDisplayed()
  })
})

در اولین تست اطمینان می‌دهیم که کاربر می‌تواند فرم را با موفقیت ارسال کند. ما به آدرس url برنامه خود می‌رویم، از متد sendKeys برای وارد کردن متن در سه فیلد ورودی استفاده می‌کنیم و سپس روی دکمه ارسال کلیک کرده، سپس منتظر می‌مانیم تا متن تأیید روی صفحه ظاهر شود و تأیید کند که ارسال ما موفقیت آمیز بوده است.

توجه داشته باشید که همه سلکتورها درست مانند سلکتورهای عادی سلنیوم هستند. شما معمولا عناصری را با استفاده از سلکتورهای CSS یا با استفاده از سلکتور XPath پیدا خواهید کرد.

در دومین تست اطمینان می‌دهیم که در صورت وجود ورودی‌های نامعتبر در صفحه، از ارسال فرم توسط کاربر جلوگیری می‌شود. ابتدا به آدرس url برنامه خود می‌رویم و سپس بدون پر کردن هیچ یک از فیلدهای ورودی، بلافاصله روی دکمه ارسال کلیک می‌کنیم. سپس می‌بینیم که سه پیام خطا روی صفحه نمایش داده می‌شود.

همچنین توجه داشته باشید که ما برخی از تنظیمات تست به اشتراک گذاشته شده را در بلوک‌های beforeEach و afterEach استخراج کرده‌ایم. سپس در بلوک beforeEach درایور وب خود را برای کروم ایجاد می‌کنیم. در بلوک afterEach هم درایور را خارج می‌کنیم.

اجرای تست‌های E2E

نتیجه کار را در این بخش مشاهده خواهید کرد. بنابراین بیایید تست‌های e2e خود را اجرا کنیم. در ترمینال yarn test:e2e:ci را برای راه اندازی برنامه و اجرای تست‌های e2e اجرا می‌کنیم. و سپس هر دو تست با موفقیت پشت سر گذاشته می‌شوند. بعد باید برنامه را در مرورگر کروم باز کنید تا مراحل انجام هر تست و نتایج آنها را در ترمینال ببینید:

اجرای تست e2e در ترمینال

TestProject حتی داشبورد گزارش خود را به صورت رایگان و به صورت گزارشات محلی HTML و PDF ارائه می‌دهد تا بتوانید نتایج تست را در مرورگر مشاهده کنید. این امر هنگام مشاهده تست‌هایی که به عنوان بخشی از خط لوله CI اجرا می‌شوند، عالی است. در زیر گزارش ما پس از دوبار اجرای این تست است:

داشبورد گزارش TestProject

جمع بندی

خوب دیدید که مراحل کار را چگونه انجام دادیم. ما با استفاده از React ، Jest و TestProject JavaScript OpenSDK تست‌های end-to-end را نوشتیم و اجرا کردیم (ماموریت با موفقیت انجام شد).

اما توجه داشته باشید که این تنها نوک کوه یخ است. TestProject مملو از امکانات پنهان دیگر مانند توانایی نوشتن تست‌های مستقیم در مرورگر با استفاده از رابط کاربری گرافیکی، تعداد زیادی پلاگین برای اقدامات مورد نیاز و ادغام با برنامه‌هایی مانند Slack به منظور ارسال پیام‌های گزارش تست نیز می‌باشد.

چه کسی می‌داند آینده دنیای اتوماسیون تست‌های e2e چگونه خواهد بود، اما TestProject مطمئنا پلتفرمی است که ارزش استفاده را دارد.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
عرفان حشمتی @heshmati74
مهندس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو