Skip to content

Commit

Permalink
New meta action: .overrideStartTime()
Browse files Browse the repository at this point in the history
  • Loading branch information
Joris-van-der-Wel committed Nov 13, 2017
1 parent ab73443 commit 0844582
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 13 deletions.
3 changes: 2 additions & 1 deletion lib/Bluefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ class Bluefox extends ExpressionChainable {
this.onCheckEnd = null; // ({expression, executionId, resultPromise}) => undefined
}

createNextExpression(action, {timeoutMs = 30000, thenable = true} = {}) {
createNextExpression(action, {timeoutMs = 30000, thenable = true, overrideStartTime} = {}) {
return new Expression({
previous: null,
action: action,
timeoutMs,
executor: this._expressionExecutor,
onceExecutor: this._expressionOnceExecutor,
thenable,
overrideStartTime,
});
}

Expand Down
13 changes: 11 additions & 2 deletions lib/Execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,27 @@ class Execution {
throw Error('Invalid state');
}

const now = this.now();
this.didFirstCheck = true;
this.executionStart = this.now();
this.executionStart = now;

for (const expression of this.expressionChain) {
const {additionalCheckTimeout} = expression.configuration;
const {additionalCheckTimeout, overrideStartTime} = expression.configuration;

for (const timeout of additionalCheckTimeout) {
if (!this.additionalCheckTimers.has(timeout)) {
const timer = this.createTimer(timeout, this._handleAdditionalCheckTimer);
timer.schedule();
this.additionalCheckTimers.set(timeout, timer);
}
}

if (overrideStartTime === null) {
this.executionStart = now;
}
else if (typeof overrideStartTime === 'number') {
this.executionStart = overrideStartTime;
}
}

return this.check();
Expand Down
6 changes: 4 additions & 2 deletions lib/Expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if (!DEFAULT_AMOUNT_ACTION.appliesAmountCheck) {
}

class Expression extends ExpressionChainable {
constructor({previous, action, timeoutMs, executor, onceExecutor, thenable = true}) {
constructor({previous, action, timeoutMs, executor, onceExecutor, thenable = true, overrideStartTime}) {
super();

if (previous !== null && !(previous instanceof Expression)) {
Expand Down Expand Up @@ -53,6 +53,7 @@ class Expression extends ExpressionChainable {
_onceExecutor: prevConfig ? prevConfig._onceExecutor : onceExecutor,
wantsDefaultAmountCheck,
_defaultAmountExpression: null,
overrideStartTime, // undefined / null / number; null means reset the start time to Date.now()
},
});

Expand Down Expand Up @@ -99,12 +100,13 @@ class Expression extends ExpressionChainable {
return result;
}

createNextExpression(action, {timeoutMs = this.configuration.timeout, thenable = true} = {}) {
createNextExpression(action, {timeoutMs = this.configuration.timeout, thenable = true, overrideStartTime} = {}) {
return new Expression({
previous: this,
action,
timeoutMs,
thenable,
overrideStartTime,
});
}

Expand Down
14 changes: 14 additions & 0 deletions lib/ExpressionChainable.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ class ExpressionChainable {
return this.createNextExpression(new actions.Noop(), {timeoutMs});
}

/**
* @param {?number} unixTimeMs Unix time in milliseconds (same as Date.now())
* @return {ExpressionChainable}
*/
overrideStartTime(unixTimeMs) {
if (unixTimeMs !== null) {
if (typeof unixTimeMs !== 'number' || !Number.isFinite(unixTimeMs)) {
throw Error('.overrideStartTime(unixTimeMs): unixTimeMs must be a finite number or null');
}
}

return this.createNextExpression(new actions.Noop(), {overrideStartTime: unixTimeMs});
}

/**
* @return {ExpressionChainable}
*/
Expand Down
98 changes: 90 additions & 8 deletions test/unit/Execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ describe('Execution', () => {
execute: sinon.spy(result),
};
};
const createExpressionMock = (action, ancestors = [], timeout = 10000) => {
const createExpressionMock = (action, ancestors = [], {timeout = 10000, overrideStartTime = undefined} = {}) => {
const expression = Object.freeze({
getExpressionChain: sinon.spy(() => [...ancestors, expression]),
configuration: Object.freeze({
timeout,
action,
additionalCheckTimeout: Object.freeze([timeout]),
overrideStartTime,
}),
describe: sinon.spy(() => 'Expression Mock Describe'),
});
Expand Down Expand Up @@ -196,10 +197,10 @@ describe('Execution', () => {
const action2 = createActionMock(() => executePendingTag`action mock ${1 + 2 - 1} is pending`);
const action3 = createActionMock(() => executeSuccess(h1));

const expression0 = createExpressionMock(action0, [], 1234);
const expression1 = createExpressionMock(action1, [expression0], 1234);
const expression2 = createExpressionMock(action2, [expression0, expression1], 3214);
const expression3 = createExpressionMock(action3, [expression0, expression1, expression2], 1234);
const expression0 = createExpressionMock(action0, [], {timeout: 1234});
const expression1 = createExpressionMock(action1, [expression0], {timeout: 1234});
const expression2 = createExpressionMock(action2, [expression0, expression1], {timeout: 3214});
const expression3 = createExpressionMock(action3, [expression0, expression1, expression2], {timeout: 1234});
const execution = new Execution(expression3, documentObservers, 12345);
execution.createTimer = createTimerMock;
let now = 40000;
Expand Down Expand Up @@ -238,7 +239,7 @@ describe('Execution', () => {
isTrue(action1.execute.calledThrice);
isTrue(action2.execute.calledThrice);
isTrue(action3.execute.notCalled);
isTrue(execution.fulfilled); // this action is still pending
isTrue(execution.fulfilled);

await executePromise;
ok(executeError);
Expand All @@ -253,12 +254,93 @@ describe('Execution', () => {
eq(executeError.fullExpression, expression3);
});

it('Should trigger the timeout at the proper time if overrideStartTime is set to a number', async () => {
const h1 = document.createElement('h1');
const action0 = createActionMock(() => executePendingTag`action mock ${1 + 2 - 2} is pending`);
const action1 = createActionMock(() => executeSuccess(h1));

const expression0 = createExpressionMock(action0, [], {timeout: 1234, overrideStartTime: 20000});
// if overrideStartTime is undefined, do not overwrite the previous value (20000)
const expression1 = createExpressionMock(action1, [expression0], {timeout: 1234, overrideStartTime: undefined});
const execution = new Execution(expression1, documentObservers, 12345);
execution.createTimer = createTimerMock;
let now = 20500;
execution.now = () => now;

let executeError;
const executePromise = execution.execute().catch(error => {executeError = error;});
isFalse(execution.fulfilled);
eq(action0.execute.callCount, 1); // immediate first check
eq(action1.execute.callCount, 0);
isTrue(createTimerMock.calledOnce);

// we are now timed out according to overrideStartTime, but not according to now() at the time of the first execution
now = 20000 + 1234;
createTimerMock.firstCall.args[1](); // invoke the first timer
isTrue(execution.fulfilled);

await executePromise;
ok(executeError);
eq(
executeError.message,
'Wait expression timed out after 1.234 seconds because action mock 1 is pending. Expression Mock Describe'
);
eq(executeError.name, 'BluefoxTimeoutError');
eq(executeError.timeout, 1234);
eq(executeError.actionFailure, 'action mock 1 is pending');
eq(executeError.expression, expression0);
eq(executeError.fullExpression, expression1);
});

it('Should trigger the timeout at the proper time if overrideStartTime is set to null', async () => {
const h1 = document.createElement('h1');
const action0 = createActionMock(() => executePendingTag`action mock ${1 + 2 - 2} is pending`);
const action1 = createActionMock(() => executeSuccess(h1));

const expression0 = createExpressionMock(action0, [], {timeout: 1234, overrideStartTime: 20000});
// if overrideStartTime is undefined, do not overwrite the previous value (20000)
const expression1 = createExpressionMock(action1, [expression0], {timeout: 1234, overrideStartTime: null});
const execution = new Execution(expression1, documentObservers, 12345);
execution.createTimer = createTimerMock;
let now = 20500;
execution.now = () => now;

let executeError;
const executePromise = execution.execute().catch(error => {executeError = error;});
isFalse(execution.fulfilled);
eq(action0.execute.callCount, 1); // immediate first check
eq(action1.execute.callCount, 0);
isTrue(createTimerMock.calledOnce);

// we are now timed out according to overrideStartTime, but not according to now() at the time of the first execution
now = 20500 + 1234 - 1;
createTimerMock.firstCall.args[1](); // invoke the first timer
isFalse(execution.fulfilled);

// we are now timed out according according to now() at the time of the first execution
now = 20500 + 1234;
createTimerMock.firstCall.args[1](); // invoke the first timer
isTrue(execution.fulfilled);

await executePromise;
ok(executeError);
eq(
executeError.message,
'Wait expression timed out after 1.234 seconds because action mock 1 is pending. Expression Mock Describe'
);
eq(executeError.name, 'BluefoxTimeoutError');
eq(executeError.timeout, 1234);
eq(executeError.actionFailure, 'action mock 1 is pending');
eq(executeError.expression, expression0);
eq(executeError.fullExpression, expression1);
});

it('Should pass metaData about the time at execution and check start', () => {
const h1 = document.createElement('h1');
const action0 = createActionMock(() => executeSuccess(h1));
const action1 = createActionMock(() => executePendingTag`action mock is pending`);
const expression0 = createExpressionMock(action0, [], 1234);
const expression1 = createExpressionMock(action1, [expression0], 1234);
const expression0 = createExpressionMock(action0, [], {timeout: 1234});
const expression1 = createExpressionMock(action1, [expression0], {timeout: 1234});

const execution = new Execution(expression1, documentObservers, 12345);
execution.createTimer = createTimerMock;
Expand Down
20 changes: 20 additions & 0 deletions test/unit/Expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ describe('Expression', () => {
.check(element => element.scrollTop > 0)
.documentComplete()
.xpath('./../section')
.overrideStartTime(1234)
.timeout('10s')
.xpathAll('.//img')
.isDisplayed()
Expand Down Expand Up @@ -385,6 +386,25 @@ describe('Expression', () => {
});
});

describe('#overrideStartTime', () => {
it('Should create an expression with overrideStartTime set', () => {
const executor = sinon.spy();
const root = new Expression({
previous,
action: noop,
timeoutMs,
executor: async expression => executor(expression),
onceExecutor,
});

eq(root.configuration.overrideStartTime, undefined);
eq(root.overrideStartTime(123456).configuration.overrideStartTime, 123456);
eq(root.overrideStartTime(null).configuration.overrideStartTime, null);
throws(() => root.overrideStartTime('foo'), /unixTimeMs.*must.*number/i);
throws(() => root.overrideStartTime(NaN), /unixTimeMs.*must.*finite.*number/i);
});
});

describe('#selector()', () => {
it('Should pass a single string as-is', () => {
const root = new Expression({previous, action: noop, timeoutMs, executor, onceExecutor});
Expand Down

0 comments on commit 0844582

Please sign in to comment.