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") })();
Testeja que podem logar-nos en un admin panel de Django.
És molt important no deixar credencials en els arxius de codi per raons òbvies.
Per selventar aquest problema, aquest exemple utilitza la llibreria dotenv
de NPM, pel què caldrà que la instal·lis:
$ npm install dotenv
I crear un arxiu .env
com aquest:
URL=http://localhost:8000 HEADLESS=false USUARI=el-meu-username CONTRASENYA=la-contrasenya-secreta
// carreguem les llibreries const { BaseTest } = require("./BaseTest.js") const { By, until } = require("selenium-webdriver"); const assert = require('assert'); //.env require('dotenv').config() console.log(process.env) class MyTest extends BaseTest { async test() { // Login test ////////////////////////////////////////////////////// var site = process.env.URL var driver = this.driver await driver.get(site+"/admin/login/"); // 1 cercar login box let usernameInput = await driver.wait(until.elementLocated( By.id('id_username')), 10000); let passwordInput = await driver.wait(until.elementLocated( By.id('id_password')), 10000); // 2 posar usuari i pass usernameInput.sendKeys(process.env.USUARI) passwordInput.sendKeys(process.env.CONTRASENYA) // 3 boto send .click() let sendButton = await driver.wait(until.elementLocated( By.css('input[value="Iniciar sessió"]')), 10000); sendButton.click() // 4 comprovem que hem entrat let logoutButton = await driver.wait(until.elementLocated( By.xpath('//button[@type="submit"]')), 10000); var currentLogoutText = await logoutButton.getText(); var expectedText = "FINALITZAR SESSIÓ"; // assert verifica que es compleixin condicions concretes // si no es compleix, llançarà una excepció de test fallit assert( currentLogoutText==expectedText, "Login fallit.\n\tTEXT TROBAT="+currentLogoutText+"\n\tTEXT ESPERAT="+expectedText); 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:
Crea els següents tests amb la web que t'ha proporcionat el professor:
Crea un dels següents tests que t'assigni el professor: exercicis_de_test_amb_django_tutorial