selenium-state-machine
Version:
Write Selenium tests using state machines
149 lines (148 loc) • 5.68 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.declareDependencies = exports.Fsm = void 0;
const selenium_webdriver_1 = require("selenium-webdriver");
const Dependency_1 = require("./Dependency");
const Error_1 = require("./Error");
const State_1 = require("./State");
const Logger_1 = require("./Logger");
class Fsm {
constructor(context, dependencies) {
this.dependencies = dependencies;
this.states = [];
this.context = context;
this.i = 0;
this.lastI = -1;
this.timeOnState = 0;
this.running = false;
this.nameMap = new Map();
}
get timeout() {
return this.context.timeout;
}
set timeout(v) {
if (this.running) {
throw new Error_1.CriticalError('cannot change timeout when pipeline is running');
}
this.context.timeout = v;
}
addState(state) {
this.states.push(state);
this.nameMap.set(state.name, this.states.length - 1);
return this;
}
state(f, timeout) {
const state = new State_1.State({ f, timeout }, this.states.length);
return this.addState(state);
}
namedState(name, f, timeout) {
const state = new State_1.State({ f, name, timeout }, this.states.length);
return this.addState(state);
}
changeIndex(i) {
this.lastI = this.i;
if (this.i === i) {
return;
}
if (i < 0) {
throw new Error_1.CriticalError('cannot go to previous checkpoint');
}
this.i = i;
this.timeOnState = 0;
}
stop() {
this.running = false;
}
async start() {
if (this.running) {
throw new Error_1.CriticalError('pipeline is already running');
}
this.running = true;
process.on('SIGINT', () => this.running = false);
process.on('uncaughtException', () => this.running = false);
while (this.running && this.context.timeout > 0) {
if (this.i >= this.states.length) {
Logger_1.logger.info('fsm has reached the end state');
return;
}
const state = this.states[this.i];
if (state.timeout <= this.timeOnState) {
throw new Error_1.TimeoutError(`timed out on checkpoint number ${this.i + 1} // (indexing from 1)`);
}
const started = Date.now();
try {
if (this.lastI !== this.i) {
Logger_1.logger.info(`executing function in state ${state.name}`);
}
const provide = await state.execute(this.dependencies);
for (const key of Object.keys(provide.updateMap)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.dependencies[key] = provide.updateMap[key];
}
if (provide.doesRepeat()) {
continue;
}
else if (provide.doesGoNext()) {
this.changeIndex(this.i + 1);
}
else if (provide.doesGoPrevious()) {
this.changeIndex(this.i - 1);
}
else if (provide.doesTransition()) {
const index = this.nameMap.get(provide.transitionState);
if (index === undefined) {
throw new Error_1.CriticalError(`state "${provide.transitionState}" does not exist`);
}
this.changeIndex(index);
}
else {
throw new Error_1.CriticalError('unknown state transition');
}
}
catch (e) {
if (e instanceof Dependency_1.StaleDependencyReferenceError) {
Logger_1.logger.info(`stale WebElement located in ${this.states[this.i].name}`, {
element: e.dependency.value
});
if (e.dependency.provider !== undefined) {
this.changeIndex(e.dependency.provider.index);
}
else {
Logger_1.logger.error('cannot recover WebElement from stale state');
throw e;
}
}
else if (e instanceof selenium_webdriver_1.error.NoSuchElementError || e instanceof selenium_webdriver_1.error.ElementClickInterceptedError) {
continue;
}
else if (e instanceof selenium_webdriver_1.error.StaleElementReferenceError) {
// warn user it might be error
Logger_1.logger.warn(`unprotected WebElement is located in ${this.states[this.i].name}`);
continue;
}
else {
Logger_1.logger.error('non fixable unknown error', {
error: selenium_webdriver_1.error
});
throw e;
}
}
finally {
const delta = Date.now() - started;
this.timeOnState += delta;
this.context.timeout -= delta;
}
}
if (this.i !== this.states.length) {
throw new Error_1.TimeoutError();
}
}
}
exports.Fsm = Fsm;
function declareDependencies(dependencies) {
for (const key of Object.keys(dependencies)) {
dependencies[key].name = key;
}
return dependencies;
}
exports.declareDependencies = declareDependencies;