Skip to content

Commit

Permalink
feat(client): for flow handler names
Browse files Browse the repository at this point in the history
  • Loading branch information
blakebyrnes committed Feb 7, 2022
1 parent 9a8a5d7 commit 8b1b144
Show file tree
Hide file tree
Showing 10 changed files with 36 additions and 34 deletions.
1 change: 1 addition & 0 deletions client/interfaces/IFlowHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ISourceCodeLocation from '@ulixee/commons/interfaces/ISourceCodeLocation'

export default interface IFlowHandler {
id?: number;
name: string;
state: IDomState | DomState;
handlerFn: (error?: Error) => Promise<any>;
callsitePath: ISourceCodeLocation[];
Expand Down
4 changes: 3 additions & 1 deletion client/lib/CoreCommandQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,12 @@ export default class CoreCommandQueue {
}

public async runOutOfBand<T>(command: string, ...args: any[]): Promise<T> {
const commandId = this.nextCommandId;
this.commandCounter?.emitter.emit('command', command, commandId, args);
return await this.sendRequest({
command,
args,
commandId: this.nextCommandId,
commandId,
startDate: new Date(),
callsite: this.getCallsite(),
...(this.internalState.commandMetadata ?? {}),
Expand Down
11 changes: 6 additions & 5 deletions client/lib/CoreTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default class CoreTab implements IJsPathEventTarget {
if (typeof state === 'function') {
state = { all: state };
}
const handler = new DomStateHandler(state, this, callsitePath);
const handler = new DomStateHandler(state, null, this, callsitePath);
try {
await handler.waitFor(options.timeoutMs);
} catch (error) {
Expand All @@ -106,11 +106,12 @@ export default class CoreTab implements IJsPathEventTarget {
if (typeof state === 'function') {
state = { all: state };
}
const handler = new DomStateHandler(state, this, callsitePath);
const handler = new DomStateHandler(state, null, this, callsitePath);
return await handler.check();
}

public async registerFlowHandler(
name: string,
state: IDomState | DomState | IDomStateAllFn,
handlerFn: (error?: Error) => Promise<any>,
callsitePath: ISourceCodeLocation[],
Expand All @@ -119,8 +120,8 @@ export default class CoreTab implements IJsPathEventTarget {
if (typeof state === 'function') {
state = { all: state };
}
this.flowHandlers.push({ id, state, callsitePath, handlerFn });
await this.commandQueue.runOutOfBand('Tab.registerFlowHandler', state.name, id, callsitePath);
this.flowHandlers.push({ id, name, state, callsitePath, handlerFn });
await this.commandQueue.runOutOfBand('Tab.registerFlowHandler', name, id, callsitePath);
}

public async shouldRetryFlowHandler(
Expand All @@ -141,7 +142,7 @@ export default class CoreTab implements IJsPathEventTarget {
const matchingStates: IFlowHandler[] = [];
await Promise.all(
this.flowHandlers.map(async flowHandler => {
const handler = new DomStateHandler(flowHandler.state, this, flowHandler.callsitePath);
const handler = new DomStateHandler(flowHandler.state, null, this, flowHandler.callsitePath);
try {
if (await handler.check()) {
matchingStates.push(flowHandler);
Expand Down
2 changes: 0 additions & 2 deletions client/lib/DomState.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import IDomState, { IDomStateAssertions } from '@ulixee/hero-interfaces/IDomState';

export default class DomState implements IDomState {
public name?: string;
public url?: string | RegExp;
public all: (options: IDomStateAssertions) => void;

constructor(options: IDomState) {
this.name = options.name;
this.url = options.url;
this.all = options.all;
}
Expand Down
27 changes: 14 additions & 13 deletions client/lib/DomStateHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export default class DomStateHandler {
#onlyRunCallbackOnMatch = false;
#retryNumber = 0;

constructor(readonly domState: IDomState, coreTab: CoreTab, callsitePath: ISourceCodeLocation[]) {
constructor(
readonly domState: IDomState,
readonly name: string,
coreTab: CoreTab,
callsitePath: ISourceCodeLocation[],
) {
this.#coreTab = coreTab;
this.#callsite = callsitePath;
bindFunctions(this);
Expand All @@ -53,7 +58,7 @@ export default class DomStateHandler {
const listenArgs: IDomStateListenArgs = {
commands: this.#rawCommandsById,
callsite: JSON.stringify(this.#callsite),
name: this.domState.name,
name: this.name,
url: this.domState.url,
};

Expand Down Expand Up @@ -185,11 +190,9 @@ export default class DomStateHandler {
private async createAssertionSets(): Promise<IStateAndAssertion<any>[]> {
const assertionSets: IStateAndAssertion<any>[] = [];

this.domState.all(
function assert(statePromise, assertion) {
assertionSets.push([Promise.resolve(statePromise).catch(err => err), assertion]);
}
);
this.domState.all(function assert(statePromise, assertion) {
assertionSets.push([Promise.resolve(statePromise).catch(err => err), assertion]);
});

// wait for all to complete
for (const assertion of assertionSets) {
Expand Down Expand Up @@ -219,19 +222,17 @@ export default class DomStateHandler {
async () => {
const runCommands: Promise<any>[] = [];
// trigger a run so we can see commands that get triggered
result = this.domState.all(
function assert(statePromise) {
runCommands.push(Promise.resolve(statePromise).catch(() => null));
}
);
result = this.domState.all(function assert(statePromise) {
runCommands.push(Promise.resolve(statePromise).catch(() => null));
});
await Promise.all(runCommands);
},
);

if (isPromise(result)) {
throw new Error(
`DomState (${
this.domState.name ?? 'no name'
this.name ?? 'no name'
}) all(assert) returns a Promise. Each state function must have synchronous assertions.`,
);
}
Expand Down
3 changes: 2 additions & 1 deletion client/lib/Hero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,10 +489,11 @@ export default class Hero extends AwaitedEventTarget<{
}

public async registerFlowHandler(
name: string,
state: IDomState | DomState | IDomStateAllFn,
handlerCallbackFn: (error?: Error) => Promise<any>,
): Promise<void> {
return await this.activeTab.registerFlowHandler(state, handlerCallbackFn);
return await this.activeTab.registerFlowHandler(name, state, handlerCallbackFn);
}

public async triggerFlowHandlers(): Promise<void> {
Expand Down
3 changes: 2 additions & 1 deletion client/lib/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,13 +270,14 @@ export default class Tab extends AwaitedEventTarget<IEventType> {
}

public async registerFlowHandler(
name: string,
state: IDomState | DomState | IDomStateAllFn,
handlerFn: (error?: Error) => Promise<any>,
): Promise<void> {
const callsitePath = scriptInstance.getScriptCallsite();

const coreTab = await this.#coreTabPromise;
await coreTab.registerFlowHandler(state, handlerFn, callsitePath);
await coreTab.registerFlowHandler(name, state, handlerFn, callsitePath);
}

public async triggerFlowHandlers(): Promise<void> {
Expand Down
2 changes: 0 additions & 2 deletions fullstack/test/domState.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ test('can wait for a url', async () => {

await hero.goto(`${koaServer.baseUrl}/waitForDomState1`);
const state1 = hero.activeTab.waitForState({
name: 'state1',
all(assert) {
assert(hero.url, url => url.includes('waitForDomState'));
},
});
await expect(state1).resolves.toBeUndefined();

const state2 = hero.activeTab.validateState({
name: 'state2',
all(assert ) {
assert(hero.url, url => url.includes('page2'));
},
Expand Down
16 changes: 8 additions & 8 deletions fullstack/test/flowHandlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test('will trigger a flow handler when an error occurs', async () => {

const spy = jest.fn();

await hero.registerFlowHandler(
await hero.registerFlowHandler('flow',
assert => {
assert(hero.querySelector('.ready').$isVisible, false);
},
Expand All @@ -56,22 +56,22 @@ test('can handle multiple simultaneous handlers', async () => {
});
const hero = await openBrowser('/simulflow');

await hero.registerFlowHandler(
await hero.registerFlowHandler('flow',
assert => assert(hero.querySelector('#test').$isVisible),
() => Promise.resolve(),
);
await hero.registerFlowHandler(
await hero.registerFlowHandler('flow2',
assert => assert(hero.url, 'https://test.com'),
() => Promise.resolve(),
);
await hero.registerFlowHandler(
await hero.registerFlowHandler('flow3',
assert => {
assert(hero.querySelector('h3').$exists);
assert(hero.url, 'https://test2.com');
},
() => Promise.resolve(),
);
await hero.registerFlowHandler(
await hero.registerFlowHandler('flow',
assert => assert(hero.findResource({ httpRequest: { statusCode: 403 } })),
() => Promise.resolve(),
);
Expand All @@ -90,7 +90,7 @@ test('bubbles up handler errors to the line of code that triggers the handlers',

const spy = jest.fn();

await hero.registerFlowHandler(
await hero.registerFlowHandler('flow',
{
all(assert) {
assert(hero.querySelector('.ready').$isVisible, false);
Expand Down Expand Up @@ -135,7 +135,7 @@ test('checks multiple handlers', async () => {

const popupSpy = jest.fn();

await hero.registerFlowHandler(
await hero.registerFlowHandler('flow',
{
all(assert) {
assert(hero.querySelector('.popup').$isVisible);
Expand All @@ -148,7 +148,7 @@ test('checks multiple handlers', async () => {
);

const linkSpy = jest.fn();
await hero.registerFlowHandler(
await hero.registerFlowHandler('flow',
{
all(assert) {
assert(hero.querySelector('.wanted').$isVisible, false);
Expand Down
1 change: 0 additions & 1 deletion interfaces/IDomState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export default interface IDomState {
name?: string;
url?: string | RegExp;
all: IDomStateAllFn;
}
Expand Down

0 comments on commit 8b1b144

Please sign in to comment.