Skip to content

Commit

Permalink
feat(core): find resources
Browse files Browse the repository at this point in the history
  • Loading branch information
blakebyrnes committed Feb 1, 2022
1 parent b58ec30 commit 3213c91
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 20 deletions.
8 changes: 8 additions & 0 deletions client/lib/CoreTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { CanceledPromiseError } from '@ulixee/commons/interfaces/IPendingWaitEve
import DomState from './DomState';
import ISourceCodeLocation from '@ulixee/commons/interfaces/ISourceCodeLocation';
import IDomState from '@ulixee/hero-interfaces/IDomState';
import IResourceFilterProperties from '@ulixee/hero-interfaces/IResourceFilterProperties';

export default class CoreTab implements IJsPathEventTarget {
public tabId: number;
Expand Down Expand Up @@ -171,6 +172,13 @@ export default class CoreTab implements IJsPathEventTarget {
return await this.commandQueue.run('Tab.goForward', options);
}

public async findResource(
filter: IResourceFilterProperties,
options?: { sinceCommandId?: number },
): Promise<IResourceMeta> {
return await this.commandQueue.run('Tab.findResource', filter, options);
}

public async reload(options: { timeoutMs?: number }): Promise<IResourceMeta> {
return await this.commandQueue.run('Tab.reload', options);
}
Expand Down
8 changes: 8 additions & 0 deletions client/lib/Hero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import DomState from './DomState';
import ConnectionToCore from '../connections/ConnectionToCore';
import CoreSession from './CoreSession';
import InternalProperties from './InternalProperties';
import IResourceFilterProperties from '@ulixee/hero-interfaces/IResourceFilterProperties';

export const DefaultOptions = {
defaultBlockedResourceTypes: [BlockedResourceType.None],
Expand Down Expand Up @@ -252,6 +253,13 @@ export default class Hero extends AwaitedEventTarget<{
await coreTab.close();
}

public async findResource(
filter: IResourceFilterProperties,
options?: { sinceCommandId: number },
): Promise<Resource> {
return await this.activeTab.findResource(filter, options);
}

public async getCollectedResources(
sessionIdPromise: Promise<string>,
name: string,
Expand Down
14 changes: 14 additions & 0 deletions client/lib/Resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ResourceResponse, { createResourceResponse } from './ResourceResponse';
import { createWebsocketResource } from './WebsocketResource';
import IWaitForResourceFilter from '../interfaces/IWaitForResourceFilter';
import Tab, { getCoreTab } from './Tab';
import IResourceFilterProperties from '@ulixee/hero-interfaces/IResourceFilterProperties';

const propertyKeys: (keyof Resource)[] = [
'url',
Expand Down Expand Up @@ -75,6 +76,19 @@ export default class Resource {
return inspectInstanceProperties(this, propertyKeys as any);
}

public static async findLatest(
tab: Tab,
filter: IResourceFilterProperties,
options: { sinceCommandId: number },
): Promise<Resource> {
const coreTab = await getCoreTab(tab);
const resourceMeta = await coreTab.findResource(filter, options);
if (resourceMeta) {
return createResource(Promise.resolve(coreTab), resourceMeta);
}
return null;
}

public static async waitFor(
tab: Tab,
filter: IWaitForResourceFilter,
Expand Down
8 changes: 8 additions & 0 deletions client/lib/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import IScreenshotOptions from '@ulixee/hero-interfaces/IScreenshotOptions';
import AwaitedPath from 'awaited-dom/base/AwaitedPath';
import { INodeVisibility } from '@ulixee/hero-interfaces/INodeVisibility';
import IResourceFilterProperties from '@ulixee/hero-interfaces/IResourceFilterProperties';
import * as Util from 'util';
import CoreTab from './CoreTab';
import Resource, { createResource } from './Resource';
Expand Down Expand Up @@ -150,6 +151,13 @@ export default class Tab extends AwaitedEventTarget<IEventType> {

// METHODS

public findResource(
filter: IResourceFilterProperties,
options?: { sinceCommandId: number },
): Promise<Resource> {
return Resource.findLatest(this, filter, options);
}

public async fetch(request: Request | string, init?: IRequestInit): Promise<Response> {
return await this.mainFrameEnvironment.fetch(request, init);
}
Expand Down
4 changes: 2 additions & 2 deletions core/lib/JsPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ export class JsPath {
if (typeof jsPath[0] === 'number') {
const paths = this.getJsPathHistoryForNode(jsPath[0]);
for (const path of paths) {
const result = await this.runJsPath<any>('exec', path.jsPath, containerOffset);
const result = await this.getNodePointer(path.jsPath, containerOffset);
const nodeId = result.nodePointer?.id;
if (nodeId !== path.nodeId) {
if (nodeId && nodeId !== path.nodeId) {
this.logger.info('JsPath.nodeRedirectFound', {
sourceNodeId: path.nodeId,
newNodeId: nodeId,
Expand Down
19 changes: 13 additions & 6 deletions core/lib/Tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import Resolvable from '@ulixee/commons/lib/Resolvable';
import { INodePointer } from '@ulixee/hero-interfaces/AwaitedDom';
import INavigation from '@ulixee/hero-interfaces/INavigation';
import injectedSourceUrl from '@ulixee/hero-interfaces/injectedSourceUrl';
import IResourceFilterProperties from '@ulixee/hero-interfaces/IResourceFilterProperties';
import IDomStateListenArgs from '@ulixee/hero-interfaces/IDomStateListenArgs';
import FrameNavigations from './FrameNavigations';
import CommandRecorder from './CommandRecorder';
import FrameEnvironment from './FrameEnvironment';
import IResourceFilterProperties from '../interfaces/IResourceFilterProperties';
import InjectedScripts from './InjectedScripts';
import Session from './Session';
import FrameNavigationsObserver from './FrameNavigationsObserver';
Expand Down Expand Up @@ -182,6 +182,7 @@ export default class Tab
this.commandRecorder = new CommandRecorder(this, this.session, this.id, this.mainFrameId, [
this.focus,
this.dismissDialog,
this.findResource,
this.getFrameEnvironments,
this.goto,
this.goBack,
Expand Down Expand Up @@ -370,17 +371,23 @@ export default class Tab
}
}

public findResource(filter: IResourceFilterProperties): IResourceMeta {
public findResource(
filter: IResourceFilterProperties,
options?: { sinceCommandId: number },
): Promise<IResourceMeta> {
// escape query string ? so it can run as regex
if (typeof filter.url === 'string') {
filter.url = stringToRegex(filter.url);
}
for (const resourceMeta of this.session.resources.getForTab(this.id)) {
if (this.isResourceFilterMatch(resourceMeta, filter)) {
return resourceMeta;
const sinceCommandId =
options?.sinceCommandId ?? this.navigations.lastHttpNavigationRequest?.startCommandId;
// find latest resource
for (const resourceMeta of this.session.resources.getForTab(this.id).reverse()) {
if (this.isResourceFilterMatch(resourceMeta, filter, sinceCommandId)) {
return Promise.resolve(resourceMeta);
}
}
return null;
return Promise.resolve(null);
}

public findStorageChange(
Expand Down
28 changes: 18 additions & 10 deletions docs/main/BasicInterfaces/Hero.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const Hero = require('@ulixee/hero');
(async () => {
// connection established here
const hero = await new Hero({
userAgent: '~ mac 13.1 & chrome > 14'
userAgent: '~ mac 13.1 & chrome > 14',
});
})();
```
Expand Down Expand Up @@ -146,9 +146,9 @@ NOTE: if using the default hero, this object will be populated with command line

### hero.isAllContentLoaded {#is-all-content-loaded}

`True` if the "load" event has triggered on the active tab.
`True` if the "load" event has triggered on the active tab.

NOTE: this event does not fire in some circumstances (such as a long-loading asset). You frequently just want to know if the page has loaded for a user (see [isPaintingStable](#is-painting-stable)).
NOTE: this event does not fire in some circumstances (such as a long-loading asset). You frequently just want to know if the page has loaded for a user (see [isPaintingStable](#is-painting-stable)).

Wait for this event to trigger with [Tab.waitForLoad(AllContentLoaded)](/docs/basic-interfaces/tab#wait-for-load).

Expand All @@ -159,7 +159,7 @@ Alias for [Tab.isAllContentLoaded](/docs/basic-interfaces/tab#is-all-content-loa
### hero.isDomContentLoaded {#is-dom-content-loaded}

`True` if the "DOMContentLoaded" event has triggered on the active tab.

Wait for this event to trigger with [Tab.waitForLoad(DomContentLoaded)](/docs/basic-interfaces/tab#wait-for-load)

#### **Type**: `Promise<boolean>`
Expand All @@ -168,7 +168,7 @@ Alias for [Tab.isDomContentLoaded](/docs/basic-interfaces/tab#is-dom-content-loa

### hero.isPaintingStable {#is-painting-stable}

`True` if the page has loaded the main content above the fold. Works on javascript-rendered pages.
`True` if the page has loaded the main content above the fold. Works on javascript-rendered pages.

Wait for this event to trigger with [Hero.waitForPaintingStable()](#wait-for-painting-stable)

Expand Down Expand Up @@ -231,13 +231,14 @@ const Hero = require('@ulixee/hero');
const document = hero.document;

for (const link of await document.querySelectorAll('a')) {
hero.output.push({ // will display in Replay UI.
hero.output.push({
// will display in Replay UI.
text: await link.textContent,
href: await link.href,
});
}
console.log(hero.output);

console.log(hero.output);
await hero.close();
})();
```
Expand Down Expand Up @@ -429,11 +430,11 @@ Refer to the [Interactions page](/docs/basic-interfaces/interactions) for detail

### hero.use*(plugin)*

Add a plugin to the current instance. This must be called before any other hero methods.
Add a plugin to the current instance. This must be called before any other hero methods.

#### **Arguments**:

- plugin `ClientPlugin` | `array` | `object` | `string`
- plugin `ClientPlugin` | `array` | `object` | `string`

#### **Returns**: `this` The same Hero instance (for optional chaining)

Expand All @@ -451,6 +452,7 @@ hero.use('@ulixee/tattle-plugin');
The following three examples all work:

Use an already-imported plugin:

```javascript
import Hero from '@ulixee/hero';
import ExecuteJsPlugin from '@ulixee/execute-js-plugin';
Expand All @@ -460,6 +462,7 @@ hero.use(ExecuteJsPlugin);
```

Use an NPM package name (if it's publicly available):

```javascript
import Hero from '@ulixee/hero';

Expand All @@ -468,6 +471,7 @@ hero.use('@ulixee/execute-js-plugin');
```

Use an absolute path to file that exports one or more plugins:

```javascript
import Hero from '@ulixee/hero';

Expand Down Expand Up @@ -505,6 +509,10 @@ Hero instances have aliases to all top-level Tab methods. They will be routed to

Alias for [Tab.fetch()](/docs/basic-interfaces/tab#fetch)

### hero.findResource*(filter, options)* {#find-resource}

Alias for [Tab.findResource()](/docs/basic-interfaces/tab#find-resource)

### hero.getFrameEnvironment*(frameElement)* {#get-frame-environment}

Alias for [Tab.getFrameEnvironment()](/docs/basic-interfaces/tab#get-frame-environment)
Expand Down
17 changes: 17 additions & 0 deletions docs/main/BasicInterfaces/Tab.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,23 @@ Get the [FrameEnvironment](/docs/basic-interfaces/frame-environment) object corr

Alias for [FrameEnvironment.getFrameEnvironment](/docs/basic-interfaces/frame-environment#get-frame-environment)

### tab.findResource*(filter, options)* {#find-resource}

Find a specific image, stylesheet, script, websocket or other resource that has been received. This function will return the most recently received resource first.

#### **Arguments**:

- filter `object` Match on "all" of the provided filters:
- url `string | RegExp` A string or regex to match a url.
- type [`ResourceType`](/docs/advanced/resource#type) A resource type to filter by.
- httpResource `object`
- statusCode `number` Http status code to filter by.
- method `string` Http request method to filter by.
- options `object` Accepts any of the following:
- sinceCommandId `number`. A `commandId` from which to look for resources. Defaults to the last Http Navigation performed on the tab.

#### **Returns**: [`Promise<Resource>`](/docs/advanced/resource)

### tab.focus*()* {#focus}

Make this tab the `activeTab` within a browser, which directs many Hero methods to this tab.
Expand Down
14 changes: 14 additions & 0 deletions fullstack/test/resources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ describe('basic resource tests', () => {
}
});

it('can find resources', async () => {
const exampleUrl = `${koaServer.baseUrl}/resources-test`;
const hero = new Hero();
Helpers.needsClosing.push(hero);

await hero.goto(exampleUrl);
await hero.waitForPaintingStable();
const elem = hero.document.querySelector('a');
await hero.click(elem);

await hero.waitForResource({ url: '/ajax?counter' });
await expect(hero.findResource({ url: '/ajax?counter=0' })).resolves.toBeTruthy();
});

it('cancels a pending resource on hero close', async () => {
const exampleUrl = `${koaServer.baseUrl}/resources-test`;
const hero = new Hero();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import IResourceType from '@ulixee/hero-interfaces/IResourceType';
import IResourceType from './IResourceType';

export default interface IResourceFilterProperties {
url?: string | RegExp;
Expand Down
2 changes: 1 addition & 1 deletion timetravel/lib/DomStateGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import DomChangesTable, {
import SessionDb from '@ulixee/hero-core/dbs/SessionDb';
import IResourceSummary from '@ulixee/hero-interfaces/IResourceSummary';
import IDomStateAssertionBatch from '@ulixee/hero-interfaces/IDomStateAssertionBatch';
import IResourceFilterProperties from '@ulixee/hero-core/interfaces/IResourceFilterProperties';
import IResourceFilterProperties from '@ulixee/hero-interfaces/IResourceFilterProperties';
import { NodeType } from './DomNode';
import DomRebuilder from './DomRebuilder';
import MirrorPage from './MirrorPage';
Expand Down

0 comments on commit 3213c91

Please sign in to comment.