feat(rpc): merge ChannelOwner and ConnectionScope (#2911)

This commit is contained in:
Dmitry Gozman 2020-07-10 15:11:47 -07:00 committed by GitHub
parent 54f9a0dd7b
commit b6fd4dc56c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 148 additions and 158 deletions

View file

@ -19,7 +19,6 @@ import { BrowserChannel, BrowserInitializer } from '../channels';
import { BrowserContext } from './browserContext';
import { Page } from './page';
import { ChannelOwner } from './channelOwner';
import { ConnectionScope } from './connection';
import { Events } from '../../events';
import { CDPSession } from './cdpSession';
@ -37,13 +36,13 @@ export class Browser extends ChannelOwner<BrowserChannel, BrowserInitializer> {
return browser ? Browser.from(browser) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: BrowserInitializer) {
super(scope, guid, initializer, true);
constructor(parent: ChannelOwner, guid: string, initializer: BrowserInitializer) {
super(parent, guid, initializer, true);
this._channel.on('close', () => {
this._isConnected = false;
this.emit(Events.Browser.Disconnected);
this._isClosedOrClosing = true;
this._scope.dispose();
this._dispose();
});
this._closedPromise = new Promise(f => this.once(Events.Browser.Disconnected, f));
}

View file

@ -23,7 +23,6 @@ import { BrowserContextChannel, BrowserContextInitializer } from '../channels';
import { ChannelOwner } from './channelOwner';
import { helper } from '../../helper';
import { Browser } from './browser';
import { ConnectionScope } from './connection';
import { Events } from '../../events';
import { TimeoutSettings } from '../../timeoutSettings';
import { CDPSession } from './cdpSession';
@ -51,8 +50,8 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
return context ? BrowserContext.from(context) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: BrowserContextInitializer) {
super(scope, guid, initializer, true);
constructor(parent: ChannelOwner, guid: string, initializer: BrowserContextInitializer) {
super(parent, guid, initializer, true);
initializer.pages.forEach(p => {
const page = Page.from(p);
this._pages.add(page);
@ -225,7 +224,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
}
this._pendingWaitForEvents.clear();
this.emit(Events.BrowserContext.Close);
this._scope.dispose();
this._dispose();
}
async close(): Promise<void> {

View file

@ -16,7 +16,6 @@
import { ChildProcess } from 'child_process';
import { BrowserServerChannel, BrowserServerInitializer } from '../channels';
import { ConnectionScope } from './connection';
import { ChannelOwner } from './channelOwner';
import { Events } from '../../events';
@ -25,8 +24,8 @@ export class BrowserServer extends ChannelOwner<BrowserServerChannel, BrowserSer
return (server as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: BrowserServerInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: BrowserServerInitializer) {
super(parent, guid, initializer);
this._channel.on('close', () => this.emit(Events.BrowserServer.Close));
}

View file

@ -19,7 +19,6 @@ import { BrowserTypeChannel, BrowserTypeInitializer } from '../channels';
import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { ConnectionScope } from './connection';
import { BrowserServer } from './browserServer';
export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeInitializer> {
@ -28,8 +27,8 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
return (browserTyep as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: BrowserTypeInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: BrowserTypeInitializer) {
super(parent, guid, initializer);
}
executablePath(): string {

View file

@ -15,7 +15,6 @@
*/
import { CDPSessionChannel, CDPSessionInitializer } from '../channels';
import { ConnectionScope } from './connection';
import { ChannelOwner } from './channelOwner';
import { Protocol } from '../../chromium/protocol';
@ -30,11 +29,11 @@ export class CDPSession extends ChannelOwner<CDPSessionChannel, CDPSessionInitia
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(scope: ConnectionScope, guid: string, initializer: CDPSessionInitializer) {
super(scope, guid, initializer, true);
constructor(parent: ChannelOwner, guid: string, initializer: CDPSessionInitializer) {
super(parent, guid, initializer, true);
this._channel.on('event', ({ method, params }) => this.emit(method, params));
this._channel.on('disconnected', () => this._scope.dispose());
this._channel.on('disconnected', () => this._dispose());
this.on = super.on;
this.addListener = super.addListener;

View file

@ -16,18 +16,33 @@
import { EventEmitter } from 'events';
import { Channel } from '../channels';
import { ConnectionScope } from './connection';
import { Connection } from './connection';
import { assert } from '../../helper';
export abstract class ChannelOwner<T extends Channel, Initializer> extends EventEmitter {
export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}> extends EventEmitter {
private _connection: Connection;
private _isScope: boolean;
// Parent is always "isScope".
private _parent: ChannelOwner | undefined;
// Only "isScope" channel owners have registered objects inside.
private _objects = new Map<string, ChannelOwner>();
readonly _guid: string;
readonly _channel: T;
readonly _initializer: Initializer;
readonly _scope: ConnectionScope;
readonly guid: string;
constructor(scope: ConnectionScope, guid: string, initializer: Initializer, isScope?: boolean) {
constructor(parent: ChannelOwner | Connection, guid: string, initializer: Initializer, isScope?: boolean) {
super();
this.guid = guid;
this._scope = isScope ? scope.createChild(guid) : scope;
this._connection = parent instanceof Connection ? parent : parent._connection;
this._guid = guid;
this._isScope = !!isScope;
this._parent = parent instanceof Connection ? undefined : parent;
this._connection._objects.set(guid, this);
if (this._parent)
this._parent._objects.set(guid, this);
const base = new EventEmitter();
this._channel = new Proxy(base, {
get: (obj: any, prop) => {
@ -45,10 +60,35 @@ export abstract class ChannelOwner<T extends Channel, Initializer> extends Event
return obj.addListener;
if (prop === 'removeEventListener')
return obj.removeListener;
return (params: any) => scope.sendMessageToServer({ guid, method: String(prop), params });
return (params: any) => this._connection.sendMessageToServer({ guid, method: String(prop), params });
},
});
(this._channel as any)._object = this;
this._initializer = initializer;
}
_dispose() {
assert(this._isScope);
// Clean up from parent and connection.
if (this._parent)
this._parent._objects.delete(this._guid);
this._connection._objects.delete(this._guid);
// Dispose all children.
for (const [guid, object] of [...this._objects]) {
if (object._isScope)
object._dispose();
else
this._connection._objects.delete(guid);
}
this._objects.clear();
}
_debugScopeState(): any {
return {
_guid: this._guid,
objects: this._isScope ? Array.from(this._objects.values()).map(o => o._debugScopeState()) : undefined,
};
}
}

View file

@ -32,18 +32,24 @@ import { parseError } from '../serializers';
import { BrowserServer } from './browserServer';
import { CDPSession } from './cdpSession';
import { Playwright } from './playwright';
import { Channel } from '../channels';
class Root extends ChannelOwner<Channel, {}> {
constructor(connection: Connection) {
super(connection, '', {}, true);
}
}
export class Connection {
readonly _objects = new Map<string, ChannelOwner<any, any>>();
readonly _waitingForObject = new Map<string, any>();
readonly _objects = new Map<string, ChannelOwner>();
private _waitingForObject = new Map<string, any>();
onmessage = (message: string): void => {};
private _lastId = 0;
private _callbacks = new Map<number, { resolve: (a: any) => void, reject: (a: Error) => void }>();
readonly _scopes = new Map<string, ConnectionScope>();
private _rootScript: ConnectionScope;
private _rootObject: ChannelOwner;
constructor() {
this._rootScript = this.createScope('');
this._rootObject = new Root(this);
}
async waitForObjectWithKnownName(guid: string): Promise<any> {
@ -61,13 +67,7 @@ export class Connection {
}
_debugScopeState(): any {
const scopeState: any = {};
scopeState.objects = [...this._objects.keys()];
scopeState.scopes = [...this._scopes.values()].map(scope => ({
_guid: scope._guid,
objects: [...scope._objects.keys()]
}));
return scopeState;
return this._rootObject._debugScopeState();
}
dispatch(message: string) {
@ -86,22 +86,20 @@ export class Connection {
debug('pw:channel:event')(parsedMessage);
if (method === '__create__') {
const scope = this._scopes.get(guid)!;
scope.createRemoteObject(params.type, params.guid, params.initializer);
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
return;
}
const object = this._objects.get(guid)!;
object._channel.emit(method, this._replaceGuidsWithChannels(params));
}
private _replaceChannelsWithGuids(payload: any): any {
if (!payload)
return payload;
if (Array.isArray(payload))
return payload.map(p => this._replaceChannelsWithGuids(p));
if (payload._object instanceof ChannelOwner)
return { guid: payload._object.guid };
return { guid: payload._object._guid };
if (typeof payload === 'object') {
const result: any = {};
for (const key of Object.keys(payload))
@ -111,7 +109,7 @@ export class Connection {
return payload;
}
_replaceGuidsWithChannels(payload: any): any {
private _replaceGuidsWithChannels(payload: any): any {
if (!payload)
return payload;
if (Array.isArray(payload))
@ -127,125 +125,73 @@ export class Connection {
return payload;
}
createScope(guid: string): ConnectionScope {
const scope = new ConnectionScope(this, guid);
this._scopes.set(guid, scope);
return scope;
}
}
export class ConnectionScope {
private _connection: Connection;
readonly _objects = new Map<string, ChannelOwner<any, any>>();
private _children = new Set<ConnectionScope>();
private _parent: ConnectionScope | undefined;
readonly _guid: string;
constructor(connection: Connection, guid: string) {
this._connection = connection;
this._guid = guid;
}
createChild(guid: string): ConnectionScope {
const scope = this._connection.createScope(guid);
this._children.add(scope);
scope._parent = this;
return scope;
}
dispose() {
// Take care of hierarchy.
for (const child of [...this._children])
child.dispose();
this._children.clear();
// Delete self from scopes and objects.
this._connection._scopes.delete(this._guid);
this._connection._objects.delete(this._guid);
// Delete all of the objects from connection.
for (const guid of this._objects.keys())
this._connection._objects.delete(guid);
// Clean up from parent.
if (this._parent) {
this._parent._objects.delete(this._guid);
this._parent._children.delete(this);
}
}
async sendMessageToServer(message: { guid: string, method: string, params: any }): Promise<any> {
return this._connection.sendMessageToServer(message);
}
createRemoteObject(type: string, guid: string, initializer: any): any {
private _createRemoteObject(parentGuid: string, type: string, guid: string, initializer: any): any {
const parent = this._objects.get(parentGuid)!;
let result: ChannelOwner<any, any>;
initializer = this._connection._replaceGuidsWithChannels(initializer);
initializer = this._replaceGuidsWithChannels(initializer);
switch (type) {
case 'bindingCall':
result = new BindingCall(this, guid, initializer);
result = new BindingCall(parent, guid, initializer);
break;
case 'browser':
result = new Browser(this, guid, initializer);
result = new Browser(parent, guid, initializer);
break;
case 'browserServer':
result = new BrowserServer(this, guid, initializer);
result = new BrowserServer(parent, guid, initializer);
break;
case 'browserType':
result = new BrowserType(this, guid, initializer);
result = new BrowserType(parent, guid, initializer);
break;
case 'cdpSession':
// Chromium-specific.
result = new CDPSession(this, guid, initializer);
result = new CDPSession(parent, guid, initializer);
break;
case 'context':
result = new BrowserContext(this, guid, initializer);
result = new BrowserContext(parent, guid, initializer);
break;
case 'consoleMessage':
result = new ConsoleMessage(this, guid, initializer);
result = new ConsoleMessage(parent, guid, initializer);
break;
case 'dialog':
result = new Dialog(this, guid, initializer);
result = new Dialog(parent, guid, initializer);
break;
case 'download':
result = new Download(this, guid, initializer);
result = new Download(parent, guid, initializer);
break;
case 'elementHandle':
result = new ElementHandle(this, guid, initializer);
result = new ElementHandle(parent, guid, initializer);
break;
case 'frame':
result = new Frame(this, guid, initializer);
result = new Frame(parent, guid, initializer);
break;
case 'jsHandle':
result = new JSHandle(this, guid, initializer);
result = new JSHandle(parent, guid, initializer);
break;
case 'page':
result = new Page(this, guid, initializer);
result = new Page(parent, guid, initializer);
break;
case 'playwright':
result = new Playwright(this, guid, initializer);
result = new Playwright(parent, guid, initializer);
break;
case 'request':
result = new Request(this, guid, initializer);
result = new Request(parent, guid, initializer);
break;
case 'response':
result = new Response(this, guid, initializer);
result = new Response(parent, guid, initializer);
break;
case 'route':
result = new Route(this, guid, initializer);
result = new Route(parent, guid, initializer);
break;
case 'worker':
result = new Worker(this, guid, initializer);
result = new Worker(parent, guid, initializer);
break;
default:
throw new Error('Missing type ' + type);
}
this._connection._objects.set(guid, result);
this._objects.set(guid, result);
const callback = this._connection._waitingForObject.get(guid);
const callback = this._waitingForObject.get(guid);
if (callback) {
callback(result);
this._connection._waitingForObject.delete(guid);
this._waitingForObject.delete(guid);
}
return result;
}

View file

@ -19,15 +19,14 @@ import { ConsoleMessageLocation } from '../../types';
import { JSHandle } from './jsHandle';
import { ConsoleMessageChannel, ConsoleMessageInitializer } from '../channels';
import { ChannelOwner } from './channelOwner';
import { ConnectionScope } from './connection';
export class ConsoleMessage extends ChannelOwner<ConsoleMessageChannel, ConsoleMessageInitializer> {
static from(message: ConsoleMessageChannel): ConsoleMessage {
return (message as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: ConsoleMessageInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: ConsoleMessageInitializer) {
super(parent, guid, initializer);
}
type(): string {

View file

@ -15,7 +15,6 @@
*/
import { DialogChannel, DialogInitializer } from '../channels';
import { ConnectionScope } from './connection';
import { ChannelOwner } from './channelOwner';
export class Dialog extends ChannelOwner<DialogChannel, DialogInitializer> {
@ -23,8 +22,8 @@ export class Dialog extends ChannelOwner<DialogChannel, DialogInitializer> {
return (dialog as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: DialogInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: DialogInitializer) {
super(parent, guid, initializer);
}
type(): string {

View file

@ -16,7 +16,6 @@
import * as fs from 'fs';
import { DownloadChannel, DownloadInitializer } from '../channels';
import { ConnectionScope } from './connection';
import { ChannelOwner } from './channelOwner';
import { Readable } from 'stream';
@ -25,8 +24,8 @@ export class Download extends ChannelOwner<DownloadChannel, DownloadInitializer>
return (download as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: DownloadInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: DownloadInitializer) {
super(parent, guid, initializer);
}
url(): string {

View file

@ -18,7 +18,7 @@ import * as types from '../../types';
import { ElementHandleChannel, JSHandleInitializer } from '../channels';
import { Frame } from './frame';
import { FuncOn, JSHandle, serializeArgument, parseResult } from './jsHandle';
import { ConnectionScope } from './connection';
import { ChannelOwner } from './channelOwner';
export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
readonly _elementChannel: ElementHandleChannel;
@ -31,8 +31,8 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
return handle ? ElementHandle.from(handle) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: JSHandleInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: JSHandleInitializer) {
super(parent, guid, initializer);
this._elementChannel = this._channel as ElementHandleChannel;
}

View file

@ -25,7 +25,6 @@ import { JSHandle, Func1, FuncOn, SmartHandle, serializeArgument, parseResult }
import * as network from './network';
import { Response } from './network';
import { Page } from './page';
import { ConnectionScope } from './connection';
import { normalizeFilePayloads } from '../serializers';
export type GotoOptions = types.NavigateOptions & {
@ -50,8 +49,8 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
return frame ? Frame.from(frame) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: FrameInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: FrameInitializer) {
super(parent, guid, initializer);
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
if (this._parentFrame)
this._parentFrame._childFrames.add(this);

View file

@ -17,7 +17,6 @@
import { JSHandleChannel, JSHandleInitializer } from '../channels';
import { ElementHandle } from './elementHandle';
import { ChannelOwner } from './channelOwner';
import { ConnectionScope } from './connection';
import { serializeAsCallArgument, parseEvaluationResultValue } from '../../common/utilityScriptSerializers';
type NoHandles<Arg> = Arg extends JSHandle ? never : (Arg extends object ? { [Key in keyof Arg]: NoHandles<Arg[Key]> } : Arg);
@ -47,8 +46,8 @@ export class JSHandle<T = any> extends ChannelOwner<JSHandleChannel, JSHandleIni
return handle ? JSHandle.from(handle) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: JSHandleInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: JSHandleInitializer) {
super(parent, guid, initializer);
this._preview = this._initializer.preview;
this._channel.on('previewUpdated', preview => this._preview = preview);
}
@ -103,7 +102,7 @@ export function serializeArgument(arg: any): any {
};
const value = serializeAsCallArgument(arg, value => {
if (value instanceof ChannelOwner)
return { h: pushHandle(value.guid) };
return { h: pushHandle(value._guid) };
return { fallThrough: value };
});
return { value, guids };

View file

@ -19,7 +19,6 @@ import * as types from '../../types';
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
import { ChannelOwner } from './channelOwner';
import { Frame } from './frame';
import { ConnectionScope } from './connection';
import { normalizeFulfillParameters } from '../serializers';
export type NetworkCookie = {
@ -58,8 +57,8 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
return request ? Request.from(request) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: RequestInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: RequestInitializer) {
super(parent, guid, initializer);
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
if (this._redirectedFrom)
this._redirectedFrom._redirectedTo = this;
@ -138,8 +137,8 @@ export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
return (route as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: RouteInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: RouteInitializer) {
super(parent, guid, initializer);
}
request(): Request {
@ -171,8 +170,8 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
return response ? Response.from(response) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: ResponseInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: ResponseInitializer) {
super(parent, guid, initializer);
}
url(): string {

View file

@ -22,7 +22,6 @@ import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer } from '../channels';
import { ConnectionScope } from './connection';
import { parseError, serializeError } from '../serializers';
import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext';
@ -69,8 +68,8 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
return page ? Page.from(page) : null;
}
constructor(scope: ConnectionScope, guid: string, initializer: PageInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: PageInitializer) {
super(parent, guid, initializer);
this.accessibility = new Accessibility(this._channel);
this.keyboard = new Keyboard(this._channel);
this.mouse = new Mouse(this._channel);
@ -511,8 +510,8 @@ export class BindingCall extends ChannelOwner<BindingCallChannel, BindingCallIni
return (channel as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: BindingCallInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: BindingCallInitializer) {
super(parent, guid, initializer);
}
async call(func: FunctionWithSource) {

View file

@ -18,7 +18,6 @@ import { PlaywrightChannel, PlaywrightInitializer } from '../channels';
import * as types from '../../types';
import { BrowserType } from './browserType';
import { ChannelOwner } from './channelOwner';
import { ConnectionScope } from './connection';
export class Playwright extends ChannelOwner<PlaywrightChannel, PlaywrightInitializer> {
chromium: BrowserType;
@ -26,8 +25,8 @@ export class Playwright extends ChannelOwner<PlaywrightChannel, PlaywrightInitia
webkit: BrowserType;
devices: types.Devices;
constructor(scope: ConnectionScope, guid: string, initializer: PlaywrightInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: PlaywrightInitializer) {
super(parent, guid, initializer);
this.chromium = BrowserType.from(initializer.chromium);
this.firefox = BrowserType.from(initializer.firefox);
this.webkit = BrowserType.from(initializer.webkit);

View file

@ -17,7 +17,6 @@
import { Events } from '../../events';
import { assertMaxArguments } from '../../helper';
import { WorkerChannel, WorkerInitializer } from '../channels';
import { ConnectionScope } from './connection';
import { ChannelOwner } from './channelOwner';
import { Func1, JSHandle, parseResult, serializeArgument, SmartHandle } from './jsHandle';
import { Page } from './page';
@ -31,8 +30,8 @@ export class Worker extends ChannelOwner<WorkerChannel, WorkerInitializer> {
return (worker as any)._object;
}
constructor(scope: ConnectionScope, guid: string, initializer: WorkerInitializer) {
super(scope, guid, initializer);
constructor(parent: ChannelOwner, guid: string, initializer: WorkerInitializer) {
super(parent, guid, initializer);
this._channel.on('close', () => {
if (this._page)
this._page._workers.delete(this);

View file

@ -19,7 +19,7 @@ const { FFOX, CHROMIUM, WEBKIT, WIN, CHANNEL } = require('./utils').testOptions(
describe.skip(!CHANNEL)('Channels', function() {
it('should work', async({browser}) => {
expect(!!browser._channel).toBeTruthy();
expect(!!browser._connection).toBeTruthy();
});
it('should scope context handles', async({browser, server}) => {
@ -101,8 +101,8 @@ describe.skip(!CHANNEL)('Channels', function() {
async function expectScopeState(object, golden) {
const remoteState = trimGuids(await object._channel.debugScopeState());
const localState = trimGuids(object._scope._connection._debugScopeState());
expect(localState).toEqual(golden);
const localState = trimGuids(object._connection._debugScopeState());
expect(processLocalState(localState)).toEqual(golden);
expect(remoteState).toEqual(golden);
}
@ -119,3 +119,21 @@ function trimGuids(object) {
return object ? object.match(/[^@]+/)[0] : '';
return object;
}
function processLocalState(root) {
const objects = [];
const scopes = [];
const visit = (object, scope) => {
if (object._guid !== '')
objects.push(object._guid);
scope.push(object._guid);
if (object.objects) {
scope = [];
scopes.push({ _guid: object._guid, objects: scope });
for (const child of object.objects)
visit(child, scope);
}
};
visit(root, []);
return { objects, scopes };
}