chitzui
chitzui

Reputation: 4098

How to stop webdriver without crashing node js

is it possible to stop the selenium webdriver without stopping node?

I have following issue: I try to create a API tool that does some web automation when getting a get request. So I am basically running selenium webdriver on a get request to /start via Express. I want the tool to check for different elements and when it somehow fails I want it to stop selenium but NOT the node instance (since a new get request could be send).

This is my code so far:

"use strict";

const webdriver = require('selenium-webdriver'),
      Express = require('express'),
      By = webdriver.By,
      until = webdriver.until,
      Keys = webdriver.Key,
      app = new Express();

app.get("/automate", (req, res) => {
  start(res);
});

function start(res) {
    const driver = new webdriver.Builder().forBrowser('chrome').build();

    driver.get('https://www.google.com/');

    // # --- foo --- #
    let errMessage = {pos: "FOO", message: "Ooops friendly robot has some troubles!"}
    checkStep("#foo", errMessage);

    driver.findElement(By.id("foo"))
          .sendKeys("fooz");

    // # --- bar --- #
    errMessage = {pos: "BAR", message: "Ooops friendly robot has some troubles!"}
    checkStep("#bar", errMessage);

    driver.findElement(By.id("bar"))
          .sendKeys("baz");

    // etc…

    function checkStep(selector, errMessage) {
        driver.findElement(By.css(selector))
          .then(() => {
            console.log(`${selector} => found`);
          })
          .catch(err => {
            console.log(`Error: ${err}`);
            res.send(errMessage);
            driver.quit();
          });
    }
}

app.get("*", (req, res) => {
  res.send("Hello World");
});

// start the server
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
app.listen(port, err => {
  if (err) { return console.error(err); }
  console.info(`Server running on http://localhost:${port} [${env}]`);
});

it is actually working so far that when selenium do not find the element the response from the API is correct. In Selenium I get back:

{
  "pos": "FOO",
  "message": "Ooops friendly robot has some troubles!"
}

So far all good. BUT unfortunately stopping selenium is also stopping Node from running.

The error I get is following:

throw error;
        ^

WebDriverError: no such session
  (Driver info: chromedriver=2.30.477690 (c53f4ad87510ee97b5c3425a14c0e79780cdf262),platform=Ma
c OS X 10.12.5 x86_64)
    at WebDriverError

Please help, thank you!

ps: I am not using webdriverio as you can see I use this package: https://www.npmjs.com/package/selenium-webdriver

Upvotes: 1

Views: 1952

Answers (1)

chitzui
chitzui

Reputation: 4098

Ok, I got this working. It’s a bit difficult solution but it works:

Using Child Processes

Basically, every time the app gets a get request to /automate it will now create a child process in node which runs the selenium scripts (a child process is kind of like using another thread. Here is a very good tutorial on child processes):

index.js

"use strict";

const Express = require('express');
const { spawn } = require('child_process');
const data = require('./data.json');

const app = new Express();

app.get("/automate", (req, res) => {

  const child = spawn(
    process.execPath,
    [`${__dirname}/test.js`, JSON.stringify(data)],
    { stdio: ['inherit', 'inherit', 'inherit', 'pipe'] }
  );

  child.stdio[3].on('data', data => {
    const response = JSON.parse(data.toString());
    res.send(response);
    console.log(response);
    child.kill();
  });

});

app.get("*", (req, res) => {
  res.send("Hello World");
});

const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
app.listen(port, err => {
  if (err) { return console.error(err); }
  console.info(`Server running on http://localhost:${port} [${env}]`);
});

test.js

"use strict";

// hook with argument 3, that is "pipe" from parent
const Net = require('net'),
      pipe = new Net.Socket({ fd: 3 });

const data = JSON.parse(process.argv[2]);

const webdriver = require('selenium-webdriver'),
      By = webdriver.By,
      until = webdriver.until,
      Keys = webdriver.Key;

function start() {
  const driver = new webdriver.Builder().forBrowser('chrome').build();

  driver.get('https://www.google.com/');

  // # --- foo --- #
  let errMessage = {pos: "lst-ib", message: "Ooops friendly robot has some troubles!"}
  checkStep("#lst-ib")
    .sendKeys("fooz");

  driver.get('https://www.facebook.com/');
  driver.get('https://www.google.com/');
  driver.get('https://www.facebook.com/');

  // # --- bar --- #
  errMessage = {pos: "BAR", message: "Ooops friendly robot has some troubles!"}
  checkStep("#bar")
    .sendKeys("baz");

  function checkStep(selector) {
    driver.findElement(By.css(selector))
    .then(() => {
      console.log(`${selector} => found`);
    })
    .catch(err => {
      console.log(`${selector} => not found`);
      publish(errMessage);
      driver.quit();
    });
  }
}

function publish(message) {
  pipe.write(JSON.stringify(message));
}

start();

It is working like a charm: on each request opening a new child process and killing that child process if it sends some message while also responding with the message to the client. Like this you can easily have several selenium instances simultaneously.

You’re welcome.

ps: If you hate all this asyncron stuff from Selenium webdriver-sync seems like a good choice. It basically wraps the selenium code to be syncon instead of asyncron. Like that I am able to use try {} catch {} and to driver.quit(); without any errors for code that comes later. (But this comes with a disadvantage: it is actually blocking your other nodejs code.)

Upvotes: 1

Related Questions