UNPKG

selenium-state-machine

Version:
149 lines (148 loc) 5.68 kB
"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;