Selenium es pot executar en diversos llenguatges, inclòs JS amb Node.js.
Ens pot ser molt pràctic per a realitzar tests per a Cordova o el propi NodeJS.
Referències:
Si tenim un projecte NodeJS podem afegir el packages amb:
$ npm install selenium-webdriver assert child-process
Si es tracta d'un projecte en un altre llenguatge podem crear la carpeta .test
i posar els tests a dins, independents del projecte:
$ mkdir .test $ cd .test $ npm init $ npm install selenium-webdriver assert child-process
Els tests s'executaran entrant a la carpeta .test
i executant-los:
$ cd .test $ node 01-xxxx.js
Per defecte s'executarà en mode HEADLESS (sense GUI). Si volem veure el browser:
$ HEADLESS=false node 01-xxx.js
Per executar els tests amb Chrome enlloc de Firefox:
$ CHROME_TESTS=chrome node 01-xxx.js
Ens convé tenir els tests independents del codi de l'aplicació. En aquest exemple tenim una estructura amb aquests arxius. Dins src
pot haver el codi en PHP o altres llenguatges que ens interessi:
. ├── .gitignore ├── README.md ├── run.sh ├── src │ ├── index.php │ ├── register.php │ └── ... └── .test ├── BaseTest.js └── 01-page-h1.js
Cal que afegim la carpeta node_modules/
al .gitignore
del nostre projecte, encara que no estiguem treballant en NodeJS:
node_modules/
En aquests tests funcionals, els tests estan aïllats del desenvolupament i del llenguatge emprat, pel què podem fer un objecte per testejar qualsevol altre projecte. Només el fitxer run.sh
contindrà les i instruccions adients per engegar un o altre projecte.
run.sh
(o run.bat
en Windows) està a l'arrel del projecte, és a dir, al mateix nivell que la carpeta .test
#!/bin/bash # directori del script run.sh SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # entrem a la carpeta del codi font cd $SCRIPT_DIR/src # engeguem el PHP server php -S 0.0.0.0:8000
#!/bin/bash # directori del script run.sh SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) # entrem a la carpeta del codi font cd $SCRIPT_DIR # engeguem el cordova sense browser cordova serve
const {Builder, Browser, By, Key, until} = require("selenium-webdriver"); const firefox = require('selenium-webdriver/firefox'); const chrome = require('selenium-webdriver/chrome'); const { spawn } = require("child_process"); const assert = require('assert'); class BaseTest { constructor() { console.log("Constructing...") this.headless = process.env.HEADLESS=="false" ? false : true; this.browser = process.env.CHROME_TESTS ? "chrome" : "firefox"; this.cmd = null; this.driver = null; } async setUp() { console.log("HEADLESS:"+this.headless); console.log("BROWSER:"+this.browser); // run server and setup driver await this.runServer( "../run", [] ); await this.setupDriver(); // deixem temps a que el servidor es posi en marxa await this.driver.sleep(2000); } async tearDown() { console.log("Closing server..."); // parem server await this.stopServer(); // deixem temps perquè es tanquin els processos await this.driver.sleep(2000); // tanquem browser console.log("Closing Selenium driver..."); await this.driver.quit(); } async run() { await this.setUp(); try { await this.test(); } finally { await this.tearDown(); } } async test() { console.log("Empty test!"); } async setupDriver() { let firefoxOptions = new firefox.Options(); let chromeOptions = new chrome.Options(); if( this.headless ) { console.log("Running Headless Tests..."); firefoxOptions = new firefox.Options().addArguments('-headless'); chromeOptions = new chrome.Options().addArguments('--headless=new'); } if( this.browser=="chrome" ) { this.driver = await new Builder() .forBrowser(Browser.CHROME) .setChromeOptions(chromeOptions) .build(); } else { this.driver = await new Builder() .forBrowser(Browser.FIREFOX) .setFirefoxOptions(firefoxOptions) .build(); } } runServer( command, options ) { // Engeguem server amb la APP if( process.platform=="win32" ) { this.cmd = spawn(command+".bat",options,{shell:true}); } else { // linux, macos (darwin), or other this.cmd = spawn(command+".sh",options); } this.cmd.stdout.on("data", data => { console.log(`stdout: ${data}`); }); this.cmd.stderr.on("data", data => { console.log(`stderr: ${data}`); }); this.cmd.on('error', (error) => { console.log(`error: ${error.message}`); }); this.cmd.on("close", code => { console.log(`child process exited with code ${code}`); }); } async stopServer() { // tanquem servidor if( process.platform=="win32" ) { spawn("taskkill", ["/pid", this.cmd.pid, '/f', '/t']); } else { // Linux, MacOS or other await this.cmd.kill("SIGHUP") } } } // publiquem l'objecte BaseTest exports.BaseTest = BaseTest;
// carreguem les llibreries const { BaseTest } = require("./BaseTest.js") const { By, until } = require("selenium-webdriver"); const assert = require('assert'); // heredem una classe amb un sol mètode test() // emprem this.driver per utilitzar Selenium class MyTest extends BaseTest { async test() { // testejem H1 a la home page ////////////////////////////////////////////////////// await this.driver.get("http://localhost:8000/browser/www/"); var currentText = await this.driver.findElement(By.tagName("h1")).getText(); var expectedText = "Tasklist"; assert( currentText==expectedText, "Títol H1 de la pàgina principal incorrecte"); console.log("TEST OK"); } } // executem el test (async function test_example() { const test = new MyTest(); await test.run(); console.log("END") })();
Aquest exemple testeja que si deixem buit el nom, ens surt un alert
que ens avisa.
// carreguem les llibreries const { BaseTest } = require("./BaseTest.js") const { By, until } = require("selenium-webdriver"); const assert = require('assert'); // heredem una classe amb un sol mètode test() // emprem this.driver per utilitzar Selenium class MyTest extends BaseTest { async test() { // testejem login ////////////////////////////////////////////////////// await this.driver.get("http://localhost:8000/register.php"); //await this.driver.findElement(By.name("nom")).getText(); // el INPUT name="nom" està buit await this.driver.findElement(By.xpath("//button[text()='Seguent']")).click(); // comprovem que l'alert message és ERRONI await this.driver.wait(until.alertIsPresent(),2000,"ERROR TEST: després del SEGUENT ha d'aparèixer un alert amb el resultat de la validació del NOM."); let alert = await this.driver.switchTo().alert(); let alertText = await alert.getText(); let assertMessage = "El NOM no pot estar buit."; assert(alertText==assertMessage,"ERROR TEST: si el nom està buit, l'alert ha de dir: '"+assertMessage+"'."); await alert.accept(); console.log("TEST OK"); } } // executem el test (async function test_example() { const test = new MyTest(); await test.run(); console.log("END") })();
Aquest exemple inclou l'ús de prompts, que es fa com si fos un alert.
Fixeu-vos que Cordova necessita anar a la web /browser/www
.
// carreguem les llibreries const { BaseTest } = require("./BaseTest.js") const { By, until } = require("selenium-webdriver"); const assert = require('assert'); // heredem una classe amb un sol mètode test() // emprem this.driver per utilitzar Selenium class AddTaskTest extends BaseTest { async test() { // testejem afegir tasca en tasklist de Cordova ////////////////////////////////////////////////////// await this.driver.get("http://localhost:8000/browser/www/"); // cliquem botó "+" await this.driver.findElement(By.xpath("//button[text()='+']")).click(); // el prompt pel text de la tasca es tracta igual que un alert en Selenium await this.driver.wait(until.alertIsPresent(),2000,"ERROR TEST: el botó '+' d'afegir tasca ha d'obrir un prompt."); let prompt = await this.driver.switchTo().alert(); // afegim el text de la tasca i acceptem var taskText = "lalala"; prompt.sendKeys(taskText); await this.driver.sleep(1000); await prompt.accept(); await this.driver.sleep(1000); // checkejem tasca await this.driver.findElement(By.xpath("//li[text()='"+taskText+"']")).click(); console.log("TEST OK"); } } // executem el test (async function test_example() { const test = new AddTaskTest(); await test.run(); console.log("END") })();
Crea un test DelTaskTest
similar a l'anterior que: