सेलेनियम वेबड्राइवर

Selenium WebDriver एक open-source library है जो W3C WebDriver protocol के ज़रिए असली ब्राउज़र चलाती है। आपका टेस्ट एक language binding को call करता है, यहाँ JavaScript का selenium-webdriver package; binding हर command को HTTP request बनाकर chromedriver या geckodriver जैसे browser driver को भेजती है; और driver उसे ब्राउज़र से करवाता है। await driver.findElement(By.css('[data-testid=login-submit]')).click() दो requests बन जाता है: एक button ढूँढती है, दूसरी उसे वैसे click करती है जैसे user करता है।

Selenium एक library है, test framework नहीं। Runner आप चुनते हैं (इस lesson में Mocha, Java में JUnit या TestNG, Python में pytest) और बाक़ी ख़ुद बनाते हैं: configuration, waits, screenshots और reports। यह lesson एक छोटा JavaScript project बनाता है, उसे local और CI में चलाता है, signed-in sessions, dialogs, tabs और uploads संभालता है, और दिखाता है कि protocol कहाँ रुक जाता है।

यह क्यों ज़रूरी है

WebDriver एक W3C standard है जिसे ब्राउज़र vendors के अपने drivers implement करते हैं, और Selenium की Java, Python, C#, JavaScript और Ruby के लिए official bindings हैं। कई स्थापित suites, ख़ासकर Java टीमों में, Selenium पर बने हैं, और mobile apps के लिए Appium भी यही protocol इस्तेमाल करता है। ऐसी टीम में आने वाले टेस्टर को पहले हफ़्ते से ही ये टेस्ट चलाने, पढ़ने और ठीक करने होते हैं।

Selenium नए tools की तुलना में आपके लिए कम काम भी करता है: page load के अलावा कुछ भी application का इंतज़ार नहीं करता, जब तक टेस्ट न कहे, और flaky Selenium टेस्ट की एक आम वजह यही है। जब आप जान लेते हैं कि हर call एक request और एक जवाब है, तो error के नाम मायने रखने लगते हैं: no such element, stale element reference और element click intercepted हर एक अलग कारण की ओर इशारा करता है, और आप product defect को test defect से अलग पहचान पाते हैं।

Command का सफ़र: protocol, drivers, locators, waits और Grid

Session तब शुरू होता है जब binding capabilities के साथ POST /session भेजती है: ब्राउज़र का नाम और headless mode जैसे options। Driver नई, ख़ाली profile के साथ ब्राउज़र launch करता है और session id लौटाता है। उसके बाद हर call उसी session पर एक request है: driver.get(url) page load होने के बाद लौटता है, findElement एक DOM node का reference लौटाता है, और click() उसी reference पर काम करता है। जवाब या तो एक value होता है या W3C specification में परिभाषित error code। जब page दोबारा render होता है, पुराना node ग़ायब हो जाता है और reference stale हो जाता है, इसलिए page objects elements को सँभालकर रखने की बजाय दोबारा ढूँढते हैं।

हर ब्राउज़र vendor अपने ब्राउज़र का driver देता है: Chrome के लिए chromedriver, Firefox के लिए geckodriver, Edge के लिए msedgedriver, और safaridriver जो macOS के साथ आता है। Driver ब्राउज़र के साथ compatible होना चाहिए: chromedriver और msedgedriver का major version Chrome या Edge के major version जैसा ही होना चाहिए। Selenium 4.6 से हर release में Selenium Manager शामिल है, जो driver उपलब्ध न होने पर मेल खाता driver ढूँढता या download करता है, और बाद की releases Chrome, Firefox या Edge ख़ुद भी download कर सकती हैं।

Selenium टेस्ट ब्राउज़र को सीधे नहीं छूता: हर call browser driver को भेजी गई W3C WebDriver HTTP request है, और explicit wait टेस्ट process में खोज दोहराने वाला loop है।
  • टेस्ट process selenium-webdriver + Mocha
  • ब्राउज़र driver chromedriver · geckodriver · Grid
  • ब्राउज़र नई, ख़ाली profile
  1. 1 टेस्ट process से ब्राउज़र driver तक: Session शुरू करें Capabilities: ब्राउज़र का नाम और headless जैसे options POST /session
  2. 2 ब्राउज़र driver से ब्राउज़र तक: ब्राउज़र launch करता है
  3. 3 ब्राउज़र driver से टेस्ट process तक: Session id जवाब
  4. 4 टेस्ट process से ब्राउज़र driver तक: Login page खोलें Page load पूरा होने पर जवाब आता है POST /session/{id}/url
  5. 5 टेस्ट process से ब्राउज़र driver तक: Sign-in button खोजें css selector, [data-testid=login-submit] POST /session/{id}/element
  6. 6 ब्राउज़र driver से ब्राउज़र तक: मौजूदा DOM में खोजता है एक बार, क्योंकि implicit wait 0 है
  7. 7 ब्राउज़र driver से टेस्ट process तक: Element reference, या no such element जवाब
  8. 8 टेस्ट process आंतरिक रूप से: Explicit wait: मिलने तक या timeout तक खोज दोहराएँ driver.wait(until.elementLocated(…), 10000) यहीं चलता है, ब्राउज़र में नहीं
  9. 9 टेस्ट process से ब्राउज़र driver तक: उसी element पर click करें Find से मिले element reference {eid} के ज़रिए POST /session/{id}/element/{eid}/click
  10. 10 ब्राउज़र driver से ब्राउज़र तक: View में scroll, जाँच कि कुछ ढक नहीं रहा, फिर click वरना element click intercepted या element not interactable
  11. 11 टेस्ट process से ब्राउज़र driver तक: Session ख़त्म करें afterEach में, ताकि failure के बाद भी चले DELETE /session/{id}
  12. 12 ब्राउज़र driver से ब्राउज़र तक: ब्राउज़र बंद करता है

परिणाम: हर call एक request और एक जवाब है; page load के अलावा कुछ भी application का इंतज़ार नहीं करता, जब तक टेस्ट न कहे।

  • Locators: protocol पाँच strategies परिभाषित करता है (CSS selector, link text, partial link text, tag name और XPath); bindings By.id, By.name और By.className को CSS में बदल देती हैं। Locator strategies guide (उसका Selenium और Cypress वाला section) का क्रम अपनाएँ: पहले By.css('[data-testid=login-email]') जैसी test id, फिर stable id या name, और XPath सिर्फ़ उस text या structure के लिए जिसे CSS व्यक्त नहीं कर सकता। Playwright के getByRole जैसा कोई built-in विकल्प नहीं है।
  • Explicit waits: driver.wait(condition, ms) आपके टेस्ट process में condition को तब तक दोहराता है जब तक वह true न हो जाए, वरना TimeoutError फेंकता है। until की तैयार conditions इस्तेमाल करें: elementLocated, elementIsVisible, elementTextIs, urlContains, alertIsPresent। Implicit wait को 0 पर रखें; उसे explicit waits के साथ मिलाने से timeouts का अनुमान नहीं लगाया जा सकता।
  • Grid: Selenium Grid वही POST /session लेता है और उसे उस node पर भेज देता है जिसके पास माँगा गया ब्राउज़र है, इसलिए टेस्ट में सिर्फ़ वह address बदलता है जिससे वह बात करता है। Official Docker image selenium/standalone-chrome, यानी Grid, Chrome और chromedriver का तैयार package जिसे Docker एक अलग-थलग container के रूप में चलाता है, port 4444 पर एक node वाला Grid है।

आज़माएँ: HTML फ़ॉर्मेटर

JavaScript project install करें और उसका ढाँचा बनाएँ

एक ख़ाली folder में npm init -y चलाएँ, फिर npm install --save-dev selenium-webdriver mocha mocha-junit-reporter। कोई driver download करने की ज़रूरत नहीं। Mocha describe, it, hooks, timeouts, retries और reporters देता है; node:assert/strict assertions देता है; और mocha-junit-reporter वह XML report लिखता है जिसे CI servers पढ़ते हैं।

Settings दो जगह रहती हैं। Runner की .mocharc.json में timeouts, retries और reporter होते हैं। एक छोटा config module base URL, browser, headless mode, Grid address और test account environment variables से पढ़ता है, ताकि वही code laptop पर, Grid पर और CI में चले। हर browser session एक ही function बनाता है, इसलिए browser या options बदलना एक ही file में होता है।

  • package.json: "test": "mocha"; default रूप से Mocha test/ की हर .js file चलाता है।
  • Modules: उदाहरण Node के require() और module.exports (CommonJS) इस्तेमाल करते हैं, जो import और export का पुराना समकक्ष है, क्योंकि npm init -y से बना project CommonJS होता है, और उसमें कोई भी import Cannot use import statement outside a module error के साथ fail होता है। import इस्तेमाल करना हो तो package.json में "type": "module" set करें; तब relative imports में file extension ज़रूरी है, जैसे '../pages/LoginPage.js'।
  • .mocharc.json: हर टेस्ट के लिए 60 सेकंड का timeout, क्योंकि Mocha का default 2 सेकंड ब्राउज़र के लिए बहुत कम है; retries 0, ताकि flaky टेस्ट दिखते रहें; local पर spec reporter।
  • support/: config.js, driver.js (नीचे) और session.js। pages/: हर page का एक page object। test/: spec files। screenshots/: failure के screenshots, जिन्हें Git ignore करता है।
// package.json   "scripts": { "test": "mocha" }
// .mocharc.json  { "timeout": 60000, "retries": 0, "reporter": "spec" }

// support/config.js: values that differ between machines come from the environment
module.exports = {
  baseUrl: process.env.BASE_URL ?? 'http://localhost:3000',
  browser: process.env.BROWSER ?? 'chrome',     // or 'firefox'
  headless: process.env.HEADLESS === '1',
  gridUrl: process.env.GRID_URL,                // e.g. http://localhost:4444
  waitMs: 10_000,
  user: {
    email: process.env.TEST_USER_EMAIL,
    password: process.env.TEST_USER_PASSWORD,
  },
};

// support/driver.js: the only place that knows how a browser session is built
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const firefox = require('selenium-webdriver/firefox');
const config = require('./config');

async function buildDriver() {
  const chromeOptions = new chrome.Options();
  const firefoxOptions = new firefox.Options();
  if (config.headless) {
    chromeOptions.addArguments('--headless=new');
    firefoxOptions.addArguments('-headless');
  }
  const builder = new Builder()
    .forBrowser(config.browser)
    .setChromeOptions(chromeOptions)
    .setFirefoxOptions(firefoxOptions);
  if (config.gridUrl) builder.usingServer(config.gridUrl);   // remote browser on a Grid
  const driver = await builder.build();
  await driver.manage().setTimeouts({ implicit: 0, pageLoad: 30_000, script: 10_000 });
  return driver;
}

module.exports = { buildDriver };

एक टेस्ट चलाएँ, उसे देखें, debug करें, फिर CI में चलाएँ

कौन-से टेस्ट चलेंगे, यह runner तय करता है, Selenium नहीं। क्योंकि config module environment variables पढ़ता है, वही command आपके laptop पर headed, headless, Firefox में या Grid पर चल सकती है।

CI में नीचे वाला GitHub Actions workflow (.github/workflows/ में एक YAML file) एक नई Linux machine पर एक job चलाता है। उसकी services: key official selenium/standalone-chrome image को job के बगल में service container की तरह शुरू करती है, इसलिए CI machine पर ब्राउज़र की ज़रूरत नहीं, और image की अपनी check-grid.sh script वाला health check job को तब तक रोके रखता है जब तक Grid तैयार न हो। फिर steps npm ci से project install करते हैं और टेस्ट को port 4444 पर Grid की ओर भेजते हैं; secrets.* और vars.* repository settings में रखी values पढ़ते हैं, इसलिए file में कोई password नहीं लिखा जाता। दो बातें अहम हैं। ब्राउज़र container के अंदर चलता है, जहाँ localhost ख़ुद container है, इसलिए BASE_URL किसी deployed test environment या दूसरी service की ओर होना चाहिए। और image का पूरा version tag pin करें, जैसा docker-selenium project सलाह देता है, ताकि नई browser release रातों-रात आपके नतीजे न बदल दे। Chrome को Docker के default से ज़्यादा shared memory चाहिए, इसलिए --shm-size=2g; report और screenshots टेस्ट fail होने पर भी upload होते हैं।

  • सब कुछ: npm test। एक file: npx mocha test/account.spec.js। एक टेस्ट: -g "wrong password" जोड़ें, जो पूरे test title से match होने वाला regular expression है।
  • Selenium में headed default है: ब्राउज़र आपकी screen पर खुलता है। Headless run के लिए variable set करें: bash में HEADLESS=1 npx mocha, PowerShell में $env:HEADLESS='1'; npx mocha।
  • Debug: जिस step को जाँचना है उससे पहले debugger; statement रखें और npx mocha --inspect-brk test/account.spec.js चलाएँ। chrome://inspect या VS Code के JavaScript Debug Terminal से attach करें और ब्राउज़र खुला रहते हुए step by step चलें; --inspect-brk Mocha का timeout भी बंद कर देता है।
  • दूसरा ब्राउज़र या Grid: BROWSER=firefox npx mocha, या, अगर Docker installed है, तो docker run -d -p 4444:4444 -p 7900:7900 --shm-size=2g selenium/standalone-chrome शुरू करके GRID_URL=http://localhost:4444 के साथ चलाएँ। Port 7900 container का ब्राउज़र एक web page में दिखाता है (noVNC, default password secret)।
# .github/workflows/ui-tests.yml
name: ui-tests
on: [push, pull_request]
jobs:
  selenium:
    runs-on: ubuntu-latest
    services:
      selenium:                                    # a one-node Grid with Chrome inside
        image: selenium/standalone-chrome:latest   # pin a full version tag in real use
        ports: ['4444:4444']
        options: >-
          --shm-size=2g
          --health-cmd /opt/bin/check-grid.sh
          --health-interval 5s --health-retries 12
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
      - run: npm ci
      - run: npx mocha --reporter mocha-junit-reporter   # writes test-results.xml
        env:
          GRID_URL: http://localhost:4444
          BASE_URL: ${{ vars.STAGING_URL }}   # a deployed site, not localhost
          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: ui-test-results
          path: |
            test-results.xml
            screenshots/

आज़माएँ: YAML फ़ॉर्मेटर

Signed-in sessions, network stubs, dialogs, tabs और uploads

हर नया session ख़ाली profile से शुरू होता है, इसलिए हर टेस्ट को form से login करना पड़ता। इसकी बजाय एक बार login करें, driver.manage().getCookies() से cookies पढ़ें और हर नए ब्राउज़र में addCookie() से जोड़ें। WebDriver cookie सिर्फ़ उसी site के लिए स्वीकार करता है जो खुली है, इसलिए पहले उस site का कोई छोटा page खोलें (Selenium documentation 404 page सुझाता है), cookies जोड़ें, फिर टेस्ट वाला page खोलें। कम से कम एक टेस्ट ऐसा रखें जो form से login करे। अगर application अपना token cookie की बजाय localStorage में रखता है, तो cookies copy करने से कुछ नहीं होगा: developers से पूछें कि session कहाँ रहता है।

Classic WebDriver में network request को intercept या stub करने का कोई command नहीं है। आम उपाय ब्राउज़र के बाहर होते हैं: test environment को किसी stub server की ओर करें, या back end में ऐसा data seed करें कि वह आपकी ज़रूरत वाला case लौटाए। ब्राउज़र के अंदर standard रास्ता WebDriver BiDi है, एक W3C draft जो session में दो-तरफ़ा WebSocket connection जोड़ता है; इसे webSocketUrl: true capability से माँगा जाता है (selenium-webdriver में options.enableBidi())। इसका network module मेल खाती requests को रोक सकता है (network.addIntercept), फिर network.provideResponse से उनका जवाब दे सकता है या network.failRequest से उन्हें fail कर सकता है। Selenium अपना implementation BiDi पर ले जा रहा है, और उसकी high-level network API अभी languages और releases के बीच अलग है, इसलिए उस पर निर्भर होने से पहले मौजूदा documentation देखें।

  • Dialogs: alert, confirm और prompt page का हिस्सा नहीं हैं, इसलिए locators उन्हें नहीं देख पाते। until.alertIsPresent() से इंतज़ार करें, getText() पढ़ें, फिर accept() या dismiss(); sendKeys() prompt में type करता है। Dialog खुला रहते दूसरी commands unexpected alert open के साथ fail होती हैं। HTML से बना modal सामान्य page content है: उसके लिए locators इस्तेमाल करें।
  • नए tabs: target="_blank" वाला link नया window handle खोलता है, लेकिन driver पुराने पर ही रहता है। मौजूदा handle याद रखें, दूसरे का इंतज़ार करें, उस पर switch करें, उसे जाँचें, बंद करें और वापस switch करें।
  • Uploads: WebDriver operating system का file picker नहीं चला सकता, इसलिए sendKeys() से absolute path input[type=file] में भेजें। जब ब्राउज़र Grid पर चलता हो, file detector set करें ताकि binding पहले file को node पर copy करे।
// support/session.js: sign in once, then give every new browser the session cookies
const { until } = require('selenium-webdriver');
const { buildDriver } = require('./driver');
const { LoginPage } = require('../pages/LoginPage');
const { baseUrl, waitMs, user } = require('./config');

async function captureSession() {
  const driver = await buildDriver();
  try {
    const login = await new LoginPage(driver).open();
    await login.signIn(user.email, user.password);
    await driver.wait(until.urlContains('/account'), waitMs);
    return await driver.manage().getCookies();
  } finally {
    await driver.quit();
  }
}

async function restoreSession(driver, cookies) {
  await driver.get(`${baseUrl}/404`);   // cookies are accepted only for the open site
  for (const cookie of cookies) await driver.manage().addCookie(cookie);
}

module.exports = { captureSession, restoreSession };

// Inside a test: a link that opens a new tab
const original = await driver.getWindowHandle();
await driver.findElement(By.css('[data-testid=returns-policy]')).click();
const twoTabs = async () => (await driver.getAllWindowHandles()).length === 2;
await driver.wait(twoTabs, waitMs);
const handles = await driver.getAllWindowHandles();
await driver.switchTo().window(handles.find((h) => h !== original));
assert.equal(await driver.getTitle(), 'Returns policy');
await driver.close();                                 // closes the tab, not the session
await driver.switchTo().window(original);

// Inside a test: upload by typing the path into the input, never via the file picker
// const remote = require('selenium-webdriver/remote'); const path = require('node:path');
driver.setFileDetector(new remote.FileDetector());     // only for a remote (Grid) browser
await driver.findElement(By.css('input[type=file]'))
  .sendKeys(path.resolve('fixtures/receipt.pdf'));

आज़माएँ: कुकी एनालाइज़र

Worked example: page objects के साथ account के चार टेस्ट

एक shop के account area में test ids वाला login form है, एक account page जो user का e-mail और saved addresses दिखाता है, और एक remove button जो ब्राउज़र के native confirm() dialog से पुष्टि माँगता है। चार टेस्ट cover करते हैं: सफल login, ग़लत password, restored session और cancel किया गया removal।

Page objects के पास locators और waits हैं; tests के पास assertions, जैसा page object वाले lesson में था। before हर file में एक बार session capture करता है, beforeEach हर टेस्ट को नया ब्राउज़र देता है, और afterEach टेस्ट fail होने पर screenshot save करता है और ब्राउज़र हमेशा बंद करता है। पहले टेस्ट को assert की ज़रूरत नहीं: अगर /account कभी load नहीं होता, तो wait error फेंकता है और टेस्ट fail होता है। Java और Python bindings यही protocol अपनाती हैं; सिर्फ़ लिखने का तरीक़ा बदलता है:

  • ब्राउज़र शुरू करना: JavaScript await new Builder().forBrowser('chrome').build(); Java new ChromeDriver(); Python webdriver.Chrome()।
  • Element ढूँढना: driver.findElement(By.css(s)); Java driver.findElement(By.cssSelector(s)); Python driver.find_element(By.CSS_SELECTOR, s)।
  • Wait: driver.wait(until.elementLocated(loc), 10000); Java new WebDriverWait(driver, Duration.ofSeconds(10)).until(ExpectedConditions.presenceOfElementLocated(loc)); Python WebDriverWait(driver, 10).until(EC.presence_of_element_located(loc))।
  • Grid पर remote ब्राउज़र: .usingServer(url); Java new RemoteWebDriver(new URL(url), new ChromeOptions()); Python webdriver.Remote(command_executor=url, options=ChromeOptions())।
  • Cookies, dialogs और windows: Java बिल्कुल JavaScript जैसी है (driver.manage().addCookie(c), driver.switchTo().alert(), driver.switchTo().window(h)); Python driver.add_cookie(c), driver.switch_to.alert और driver.switch_to.window(h) इस्तेमाल करता है। Runners: Java में JUnit 5 या TestNG, Python में pytest।
// pages/LoginPage.js: locators and actions of one page; assertions stay in the tests
const { By, until } = require('selenium-webdriver');
const { baseUrl, waitMs } = require('../support/config');

const EMAIL = By.css('[data-testid=login-email]');
const PASSWORD = By.css('[data-testid=login-password]');
const SUBMIT = By.css('[data-testid=login-submit]');
const ERROR = By.css('[data-testid=login-error]');

class LoginPage {
  constructor(driver) { this.driver = driver; }

  async open() {
    await this.driver.get(`${baseUrl}/login`);
    await this.driver.wait(until.elementLocated(EMAIL), waitMs);
    return this;
  }

  async signIn(email, password) {
    await this.driver.findElement(EMAIL).sendKeys(email);
    await this.driver.findElement(PASSWORD).sendKeys(password);
    await this.driver.findElement(SUBMIT).click();
  }

  async errorText() {
    const el = await this.driver.wait(until.elementLocated(ERROR), waitMs);
    await this.driver.wait(until.elementIsVisible(el), waitMs);
    return el.getText();
  }
}

module.exports = { LoginPage };

// pages/AccountPage.js: same pattern (imports as in LoginPage.js)
const ACCOUNT_EMAIL = By.css('[data-testid=account-email]');
const ADDRESS = By.css('[data-testid=address]');
const REMOVE_ADDRESS = By.css('[data-testid=address-remove]');

class AccountPage {
  constructor(driver) { this.driver = driver; }

  async open() {
    await this.driver.get(`${baseUrl}/account`);
    await this.driver.wait(until.elementLocated(ACCOUNT_EMAIL), waitMs);
    return this;
  }

  async email() { return this.driver.findElement(ACCOUNT_EMAIL).getText(); }

  async addressCount() { return (await this.driver.findElements(ADDRESS)).length; }

  // only clicks: the confirm() dialog that opens belongs to the test
  async removeFirstAddress() { await this.driver.findElement(REMOVE_ADDRESS).click(); }
}

module.exports = { AccountPage };

// test/account.spec.js
const assert = require('node:assert/strict');
const fs = require('node:fs');
const { until } = require('selenium-webdriver');
const { buildDriver } = require('../support/driver');
const { captureSession, restoreSession } = require('../support/session');
const { LoginPage } = require('../pages/LoginPage');
const { AccountPage } = require('../pages/AccountPage');
const { waitMs, user } = require('../support/config');

describe('Account', function () {
  let driver;
  let cookies;

  before(async function () { cookies = await captureSession(); });
  beforeEach(async function () { driver = await buildDriver(); });
  afterEach(async function () {
    if (this.currentTest.state === 'failed') {
      fs.mkdirSync('screenshots', { recursive: true });
      const png = await driver.takeScreenshot();
      fs.writeFileSync(`screenshots/${this.currentTest.title}.png`, png, 'base64');
    }
    await driver.quit();
  });

  it('signs in with valid credentials', async function () {
    const login = await new LoginPage(driver).open();
    await login.signIn(user.email, user.password);
    await driver.wait(until.urlContains('/account'), waitMs);   // the wait is the check
  });

  it('rejects a wrong password and stays on the login page', async function () {
    const login = await new LoginPage(driver).open();
    await login.signIn(user.email, 'not-the-password');
    assert.equal(await login.errorText(), 'Wrong e-mail or password');
    assert.ok((await driver.getCurrentUrl()).includes('/login'));
  });

  it('opens the account of a restored session', async function () {
    await restoreSession(driver, cookies);
    const account = await new AccountPage(driver).open();
    assert.equal(await account.email(), user.email);
  });

  it('keeps the address when the removal is cancelled', async function () {
    await restoreSession(driver, cookies);
    const account = await new AccountPage(driver).open();
    const count = await account.addressCount();
    await account.removeFirstAddress();
    const dialog = await driver.wait(until.alertIsPresent(), waitMs);
    assert.equal(await dialog.getText(), 'Remove this address?');
    await dialog.dismiss();
    assert.equal(await account.addressCount(), count);
  });
});

आज़माएँ: JavaScript फ़ॉर्मेटर

आम गलतियाँ

  • Page का इंतज़ार करने के लिए driver.sleep(3000) इस्तेमाल करना, या implicit wait set करके ऊपर से explicit waits जोड़ना। driver.wait() से condition का इंतज़ार करें और implicit wait 0 रखें।
  • driver.quit() सिर्फ़ pass होने वाले टेस्ट के आख़िर में call करना। तब failed टेस्ट एक ब्राउज़र और driver process चलता छोड़ देता है, और Grid पर एक slot घेरे रहता है; afterEach या finally में quit करें।
  • Navigation या re-render से पहले मिला element रखकर बाद में इस्तेमाल करना, जो stale element reference के साथ fail होता है। Elements को उसी page object method के अंदर ढूँढें जो उन्हें इस्तेमाल करता है।
  • PATH या repository में पुराना chromedriver रखना। Selenium Manager तभी काम में आता है जब कोई driver न मिले, इसलिए पुराना driver इस्तेमाल होता है और अगले browser update के बाद fail होता है।
  • Container या Grid node पर चल रहे ब्राउज़र को localhost की ओर भेजना, जिसका वहाँ मतलब container ख़ुद है, आपकी machine या CI runner नहीं।
  • Build को green करने के लिए retries चालू करना और यह record न करना कि किन टेस्ट को retry चाहिए था। Retry flaky टेस्ट का असर सीमित करता है, उसे ठीक नहीं करता।

QA का नज़रिया

जब आप किसी Selenium suite वाली टीम में आएँ, तो पहले command line से test environment पर एक spec file चलाएँ, फिर driver factory और config पढ़ें: base URL, browser, credentials और Grid address कहाँ से आते हैं। जब कोई टेस्ट fail हो, stack trace से पहले error पढ़ें। no such element का मतलब है locator ग़लत है या page अपेक्षित state में नहीं; element click intercepted का मतलब अक्सर है कि कोई banner, overlay या spinner target को ढक रहा है; wait से आया TimeoutError बताता है कि condition कभी true नहीं हुई, जो defect भी हो सकता है और ग़लत expectation भी। Bug report या flaky-test ticket में screenshot, exact command और browser version लिखें, जो (await driver.getCapabilities()).get('browserVersion') लौटाता है।

Developers के साथ data-testid attributes पर और test environments में sessions व data बनाने के supported तरीक़े पर सहमति बनाएँ, जैसे API login या seed endpoint। जो CI सँभालता है उसके साथ pinned Grid image और चलाए जाने वाले browsers पर सहमति बनाएँ। Payment provider या single sign-on service जैसे third-party pages से गुज़रने वाले flows में provider का sandbox इस्तेमाल करें या अपनी सीमा पर रुक जाएँ, किसी और की production site को automate न करें।

अभ्यास

सिर्फ़ laptop पर चलने वाले टेस्ट को CI में चलाएँ

आपका काम

नीचे वाला टेस्ट उसके लेखक के laptop पर pass होता था, जहाँ GRID_URL set नहीं है और Chrome local चलता है। तब से दो चीज़ें बदल गई हैं। Suite अब इस lesson के CI job में चलता है, जहाँ ब्राउज़र selenium/standalone-chrome container में चलता है। और application बदल गया: Help link अब नए tab में खुलता है, और file चुनने के बाद avatar form दोबारा render होता है, जिससे उसके buttons बदल जाते हैं। अब टेस्ट fail होता है।

हर समस्या ढूँढें, बताएँ कि हर एक किस तरह का failure ला सकती है, और सुधरे हुए टेस्ट का वर्णन करें।

सामग्री
// test/profile.spec.js: passed on the author's laptop, now fails in CI
const assert = require('node:assert/strict');
const { By } = require('selenium-webdriver');
const { buildDriver } = require('../support/driver');

describe('Profile', function () {
  it('uploads an avatar and opens help', async function () {
    const driver = await buildDriver();
    await driver.get('http://localhost:3000/login');
    await driver.findElement(By.id('email')).sendKeys('sara@example.com');
    await driver.findElement(By.id('password')).sendKeys('Sara-Pass-2026');
    await driver.findElement(By.id('submit')).click();
    await driver.sleep(3000);                         // login redirects to /profile
    const save = await driver.findElement(
      By.xpath('/html/body/div[2]/main/div[3]/form/button[2]'));
    await driver.findElement(By.css('input[type=file]')).sendKeys('avatar.png');
    await save.click();
    await driver.findElement(By.linkText('Help')).click();   // opens in a new tab
    assert.equal(await driver.getTitle(), 'Help centre');
    await driver.quit();
  });
});
चरण
  1. हर hard-coded value चिह्नित करें और तय करें कि उसे कहाँ से आना चाहिए।
  2. हर वह जगह ढूँढें जहाँ टेस्ट page तैयार होने से पहले action करता है या किसी element को ज़रूरत से ज़्यादा देर रखता है।
  3. जाँचें कि ब्राउज़र remote हो और नया tab खुले तो upload और Help link कैसा व्यवहार करते हैं।
  4. टेस्ट को बाँटें, locators को page object में ले जाएँ और सुधरा हुआ version लिखें।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

Configuration: जब ब्राउज़र Grid पर चलता है तो http://localhost:3000 ख़ुद container है, इसलिए CI में page कभी load नहीं होता। Config module का baseUrl इस्तेमाल करें। E-mail और password की जगह environment variables या CI secrets हैं, code नहीं।

Timing और locators: driver.sleep(3000) एक अंदाज़ा है, धीमे runner पर बहुत छोटा और तेज़ runner पर समय की बर्बादी; until.urlContains('/profile') का इंतज़ार करें या page object के open() को इंतज़ार करने दें। Absolute XPath layout के किसी भी बदलाव से टूट जाता है; [data-testid=avatar-save] जैसी test id माँगें। save upload से पहले ढूँढा गया है और re-render उस button को बदल देता है, इसलिए save.click() stale element reference के साथ fail होता है, या तभी pass होता है जब click संयोग से re-render से पहले हो जाए; button को click करने से ठीक पहले, page object के अंदर ढूँढें।

Upload: sendKeys('avatar.png') को path.resolve('fixtures/avatar.png') जैसा absolute path चाहिए, और remote ब्राउज़र को driver.setFileDetector(new remote.FileDetector()) भी चाहिए; वरना node ऐसी file ढूँढेगा जो सिर्फ़ runner पर है। फिर नतीजा assert करें, जैसे success message, यह मानने के बजाय कि error न आना ही सफलता है।

नया tab: driver profile वाले tab पर ही रहता है, इसलिए getTitle() profile page का title पढ़ता है और assertion fail होता है। Original handle याद रखें, दो handles का इंतज़ार करें, नए पर switch करें, title "Help centre" assert करें, उसे बंद करें और वापस switch करें।

Structure: एक टेस्ट दो असंबंधित व्यवहार जाँचता है और form से login करता है, जिसे login tests पहले ही cover करते हैं। इसे "uploads an avatar" और "Help opens in a new tab" में बाँटें, दोनों में cookies से session restore करें, और driver.quit() को afterEach में ले जाएँ, जो failure के बाद भी चलता है, failure पर screenshot के साथ।

अक्सर पूछे जाने वाले सवाल

क्या मुझे अब भी chromedriver ख़ुद download करना पड़ता है?

आम तौर पर नहीं। Selenium 4.6 से, जब PATH पर कोई driver न हो, Selenium Manager मेल खाता driver ढूँढता या download करता है। Drivers ख़ुद सिर्फ़ वहाँ install करें जहाँ machine download नहीं कर सकती, और तब browser और driver के versions साथ-साथ pin करें, जैसे pinned Docker image से।

नए project के लिए Selenium चुनें या Playwright?

अपनी टीम की language, जिन browsers और devices को cover करना है, और जो infrastructure पहले से चल रहा है, उसके आधार पर तय करें। Selenium एक W3C standard अपनाता है जिसे browser vendors implement करते हैं, और इसकी bindings ज़्यादा languages में हैं; Playwright अपने runner के साथ आता है जिसमें auto-waiting, network interception और trace viewer हैं। दोनों maintainable suite सँभाल सकते हैं; suite design और test data का अनुशासन tool से ज़्यादा मायने रखते हैं।

क्या Selenium IDE और Selenium WebDriver एक ही हैं?

नहीं। Selenium IDE clicks को record और replay करने वाला tool है। WebDriver वह library और protocol है जिससे आप code के रूप में टेस्ट लिखते हैं। Recording page को explore करने के लिए उपयोगी है, लेकिन suite में आने से पहले उसे page objects, explicit waits और बेहतर locators चाहिए।

संदर्भ

  • WebDriver (W3C)W3C · 2026वह W3C प्रोटोकॉल जिसे ब्राउज़र Selenium जैसे ऑटोमेशन टूल से रिमोट कंट्रोल के लिए लागू करते हैं (Level 2 संस्करण Working Draft है)।
  • Selenium DocumentationSelenium ProjectWebDriver से ब्राउज़र ऑटोमेशन का आधिकारिक दस्तावेज़, वेट, लोकेटर और Grid सहित।
  • Waiting Strategies — SeleniumSelenium Projectब्राउज़र टेस्ट पेज से race क्यों करते हैं, और implicit व explicit waits WebDriver स्क्रिप्ट को ऐप के साथ कैसे सिंक करते हैं।
  • GitHub Actions documentationGitHubGitHub Actions में workflows, jobs, runners और artifacts कैसे परिभाषित होते हैं — जहाँ हर push या pull request पर ऑटोमेटेड टेस्ट चलते हैं।

Mutqan tools से अभ्यास करें

नेस्टेड एलिमेंट को सही इंडेंटेशन देकर HTML सुंदर बनाएँ, pre, script और style ब्लॉक सुरक्षित रखते हुए।

फ़ॉर्मेटिंग

Set-Cookie हेडर पार्स करें और Secure, HttpOnly, SameSite, Domain, Path, एक्सपायरी तथा साइज़ की सर्वोत्तम प्रथाएँ जाँचें।

वैलिडेशन

YAML को एकसमान इंडेंटेशन और quoting के साथ फ़ॉर्मेट और वैलिडेट करें।

फ़ॉर्मेटिंग

JavaScript और TypeScript कोड को एकसमान इंडेंटेशन और स्पेसिंग के साथ सुंदर बनाएँ।

फ़ॉर्मेटिंग

और गहराई से

इस topic में आपकी प्रगति

QA रोडमैप का हिस्सा

इस topic से पहले

अगले topics