Last active
August 24, 2016 23:02
-
-
Save burg/66cd1abac9d0b4c82817e1c3a44f3527 to your computer and use it in GitHub Desktop.
Web Inspector UI Testing Sketches
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function test() { | |
WUI.TabBar.activeTabTypes = [WUI.Tab.Type.Timeline]; | |
let timelineTab = WUI.tabBar.tabForType(WUI.Tab.Type.Timeline); | |
timelineTab.activeTimelineTypes = [ | |
WUI.Timeline.Type.JavaScript, | |
]; | |
// Set up local variables and state. | |
let framesTimeline = timelineTab.timelineForType(WUI.Timeline.Type.RenderingFrames); | |
let recordToSelect = null; | |
// Reproduce the behavior. | |
return Promise.resolve() | |
.then(() => timelineTab.click()) | |
// -- FIXME: replace with loading a canned recording. | |
.then(() => timelineTab.recordingButton.click()) | |
.then(() => new Promise((resolve, reject) => { setTimeout(resolve, 1000); })) | |
.then(() => timelineTab.recordingButton.click()) | |
// -- | |
.then(() => timelineTab.viewModeSelector.selectOption(WUI.TimelineTab.ViewModes.Frames)) | |
.then(() => framesTimeline.detailsView.recordLengthFilterSelector.selectOption(WUI.RenderingFramesView.RecordLengthFilters.Over15)) | |
.then(() => { | |
let records = timelineTab.visibleRecords; | |
recordToSelect = records.firstMatching((record) => record.duration > 0.015); | |
let recordBarElement = framesTimeline.barForRecord(recordToSelect); | |
let hasFilteredStyle = WUI.FramesTimeline.Predicates.filteredBarStyle; // returns (elem) => { ... proxy-defined style checks on ElementProxy ... } | |
InspectorTest.expectThat(hasFilteredStyle(recordBar), "Record bar for record over 15ms should have filtered styles applied."); | |
return recordBar.click(); | |
}) | |
.then(() => { | |
let recordRowElement = framesTimeline.detailsView.recordsTable.rowForObject(recordToSelect); | |
let hasVisibleStyle = WUI.DataGrid.Predicates.visibleRowStyle; | |
InspectorTest.expectThat(!recordRowElement || !hasVisibleStyle(recordRowElement), "Row for record that is filtered out should not be visible in table."); | |
return Promise.resolve(); | |
}) | |
.catch(InspectorTest.reportPromiseException) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function test() { | |
WUI.tabBar.activeTabTypes = [WUI.Tab.Type.Timeline]; | |
let timelineTab = WUI.tabBar.tabForType(WUI.Tab.Type.Timeline); | |
timelineTab.activeTimelineTypes = [ | |
WUI.Timeline.Type.Layout, | |
]; | |
let leftHandle = timelineTab.overview.leftRangeHandle; | |
let handlePositionStart = null; | |
let visibleIntervalAtStart = null; | |
// Reproduce the behavior. | |
Promise.resolve() | |
.then(() => timelineTab.click()) | |
.then(() => timelineTab.recordingButton.click()) | |
.then(() => new Promise((resolve, reject) => { | |
setTimeout(() => timelineTab.recordingButton.click().then(resolve), 1000) | |
}) | |
}) | |
.then(() => { | |
visibleIntervalAtStart = timelineTab.visibleInterval; | |
let [startTime, endTime] = visibleIntervalAtStart; | |
let activeInterval = Number.constrain(endTime - startTime, 0, endTime); | |
let clampedTime = activeInterval * 0.2; | |
InspectorTest.assertThat(clampedTime > 0, "Hidden timeline duration should be positive."); | |
InspectorTest.assertThat(startTime < endTime - clampedTime && startTime + clampedTime < endTime, "Hidden timeline duration should be less than visible timeline duration."); | |
timelineTab.visibleInterval = [startTime + clampedTime, endTime - clampedTime]; | |
return WUI.Signals.layoutSoon(); | |
}) | |
.then(() => { | |
handlePositionStart = leftHandle.position; | |
return WUITestHost.simulateMouseAction({ | |
target: leftHandle, | |
action: WUI.MouseAction.Down, | |
button: WUI.MouseButton.Left, | |
}); | |
} | |
.then(() => { | |
return WUITestHost.simulateMouseAction({ | |
target: leftHandle, | |
delta: {x: +50, y: +0}, | |
action: WUI.MouseAction.Move, | |
button: WUI.MouseButton.Left, | |
}); | |
}) | |
.then(() => { | |
return WUITestHost.simulateMouseAction({ | |
target: leftHandle, | |
action: WUI.MouseAction.Up, | |
button: WUI.MouseButton.Left, | |
}); | |
}) | |
.then(() => { | |
let handlePositionEnd = leftHandle.position; | |
InspectorTest.expectThat(handlePositionEnd.x > handlePositionStart.x, "Dragging the handle horizontally should change its x-position."); | |
InspectorTest.expectThat(handlePositionEnd.y === handlePositionStart.y, "Dragging the handle horizontally should not change its y-position."); | |
visibleIntervalAtEnd = timelineTab.visibleInterval; | |
InspectorTest.expectThat(visibleIntervalAtStart[0] < visibleIntervalAtEnd[1], "Start time for active interval should increase after dragging right."); | |
}) | |
.catch(InspectorTest.reportPromiseFailure); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function test() { | |
// Set up tabs and timelines needed for the test. | |
WUI.tabBar.activeTabTypes = [WUI.Tab.Type.Timeline, WUI.Tab.Type.Console]; | |
let consoleTab = WUI.tabBar.tabForType(WUI.Tab.Type.Console); | |
let timelineTab = WUI.tabBar.tabForType(WUI.Tab.Type.Timeline); | |
timelineTab.activeTimelineTypes = [ | |
WUI.Timeline.Type.Memory, | |
WUI.Timeline.Type.Allocations, | |
]; | |
let memoryTimeline = timelineTab.timelineForType(WUI.Timeline.Type.Memory); | |
let allocationsTimeline = timelineTab.timelineForType(WUI.Timeline.Type.Allocations); | |
// Reproduce the behavior. | |
Promise.resolve() | |
.then(() => timelineTab.click()) | |
.then(() => memoryTimeline.overviewLabel.click()) | |
.then(() => consoleTab.click()) | |
.then(() => timelineTab.click()) | |
.then(() => { | |
InspectorTest.expectThat(timelineTab.selectedTimeline.type === WUI.Timeline.Type.Memory); | |
return Promise.resolve(); | |
}}) | |
.then(() => allocationsTimeline.overviewLabel.click()) | |
.then(() => consoleTab.click()) | |
.then(() => timelineTab.click()) | |
.then(() => { | |
InspectorTest.expectThat(timelineTab.selectedTimeline.type === WUI.Timeline.Type.Allocations); | |
return Promise.resolve(); | |
}) | |
.catch(InspectorTest.reportPromiseFailure); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WUI.Signals { | |
static layoutSoon() | |
{ | |
// TODO: wait for the view hierarchy to finish re-layout. | |
return Promise.resolve(); | |
} | |
} | |
class WUITestHost { | |
constructor() | |
{ | |
this._pressedButtons = new Set; | |
this._pressedModifiers = new Set; | |
} | |
simulateMouseAction(args) | |
{ | |
let {target, action, button, delta} = args; | |
if (!(target instanceof WUI.ElementProxy)) | |
throw new Error("Tried to simulate interaction with something other than an ElementProxy.", target); | |
// TODO: implement this to check for problems, like invalid state changes per-button. | |
switch (action) { | |
case WUI.MouseAction.Down: | |
case WUI.MouseAction.Up: | |
case WUI.MouseAction.Move: | |
case WUI.MouseAction.SingleClick: | |
case WUI.MouseAction.DoubleClick: | |
break; | |
default: | |
throw new Error("Tried to simulate unrecognized action with button.", action, button); | |
} | |
switch (button) { | |
case WUI.MouseButton.Left: | |
case WUI.MouseButton.Right: | |
case WUI.MouseButton.Middle: | |
break; | |
default: | |
throw new Error("Tried to simulate action with unrecognized button.", action, button); | |
} | |
// TODO: simulate the interaction and wait for the UI to update. | |
return Promise.resolve(); | |
} | |
} | |
// The root class for all test proxy objects that are interactable. | |
// All subclasses must implement locate() to provide a ElementProxy instance. | |
class WUI.UIProxy { | |
// Returns a DOM element represented by this UI proxy object. | |
// To be overridden by subclasses. | |
locate() { throw new Error("This method must be overridden by subclasses."); } | |
// Helpers to shorten test case boilerplate. | |
click() { | |
return WUIFrontendHost.simulateMouseAction({ | |
target: this.locate(), | |
action: WUI.MouseAction.SingleClick, | |
button: WUI.MouseButton.Left, | |
}); | |
} | |
} | |
// For interactable elements. An ObjectProxy or ViewProxy typically | |
// gives out one ElementProxy object per interactable DOM element. | |
class WUI.ElementProxy extends WUI.UIProxy { | |
constructor(resolveCallback) { | |
this._resolveCallback = resolveCallback; | |
} | |
locate() { return this; } | |
unwrap() | |
{ | |
// Simulated interactions use unwrap() to access the raw DOM element. | |
return this._resolveCallback.call(null); | |
} | |
static fromElement(element) | |
{ | |
if (!(element instanceof Element)) | |
throw new Error("ElementProxy.fromElement was passed a non-element argument.", element); | |
return new WUI.ElementProxy(() => element); | |
} | |
} | |
// A proxy that corresponds to a specific WebInspector.Object instance | |
// that's used to represent a control composed of multiple DOM elements. | |
// It can give out other ObjectProxy or ElementProxy objects. | |
class WUI.ControlProxy extends WUI.UIProxy { | |
constructor(representedObject = null) { | |
if (representedObject) | |
} | |
// Default implementation for 1:1 proxies of WebInspector.Objects. | |
get representedObject() { return this._representedObject || null; } | |
set representedObject(value) { | |
console.assert(!this.representedObject); | |
console.assert(value instanceof WebInspector.Object); | |
this._representedObject = value; | |
} | |
// ViewProxy API - can be overridden. | |
unwrap() { return this.representedObject; } | |
// UIProxy API - must be overridden. | |
// There is no guarantee that representedObject has a 'element' getter, | |
// so there is no default implementation of locate(). | |
static fromObjects(objects) { | |
return objects.map((object) => WUI.ControlProxy.fromObject(object)); | |
} | |
static fromObject(object) { | |
if (!WUI.ControlProxy._wrapperCache) | |
WUI.ControlProxy._wrapperCache = new Map; | |
// Cache hit. | |
let result = WUI.ControlProxy._wrapperCache.get(object); | |
if (result) | |
return result; | |
// Cache miss. | |
let wrapper = WUI.ControlProxy._constructFromObject(object); | |
if (wrapper) | |
WUI.ControlProxy._wrapperCache.set(object, wrapper); | |
return wrapper; | |
} | |
static _constructFromObject(object) { | |
console.assert(object instanceof WebInspector.Object, "Cannot automatically construct a ControlProxy from a non-Object instance."); | |
if (!(object instanceof WebInspector.Object)) | |
return; | |
if (object instanceof WebInspector.TabBarItem) | |
return new WUI.TabBarItem(object); | |
console.assert("Cannot construct wrapper for unsupported Object subclass: ", view); | |
return null; | |
} | |
} | |
// A proxy that corresponds to a specific WebInspector.View instance. | |
// It can give out other ViewProxy, ObjectProxy, or ElementProxy objects. | |
// ViewProxy subclasses also mediate test access to the view's underlying | |
// represented object(s), in case a test needs to get or set model state. | |
class WUI.ViewProxy extends WUI.UIProxy | |
{ | |
constructor(representedView = null) | |
{ | |
if (representedView) | |
this.representedView = representedView; | |
} | |
// Default implementation for 1:1 proxies of views. | |
get representedView() { return this._representedView || null; } | |
set representedView(value) | |
{ | |
console.assert(!this.representedView); | |
console.assert(value instanceof WebInspector.View); | |
this._representedView = value; | |
} | |
// ViewProxy API - can be overridden. | |
unwrap() { return this.representedView; } | |
locate() { return WUI.ElementProxy.fromElement(this.unwrap().element); } | |
static fromViews(views) | |
{ | |
return views.map((view) => WUI.ViewProxy.fromView(view)); | |
} | |
static fromView(view) | |
{ | |
if (!WUI.ViewProxy._wrapperCache) | |
WUI.ViewProxy._wrapperCache = new Map; | |
// Cache hit. | |
let result = WUI.ViewProxy._wrapperCache.get(view); | |
if (result) | |
return result; | |
// Cache miss. | |
let wrapper = WUI.ViewProxy._constructFromView(view); | |
if (wrapper) | |
WUI.ViewProxy._wrapperCache.set(view, wrapper); | |
return wrapper; | |
} | |
static _constructFromView(view) | |
{ | |
console.assert(view instanceof WebInspector.View, "Cannot automatically construct a ViewProxy from a non-View instance."); | |
if (!(view instanceof WebInspector.View)) | |
return; | |
if (view instanceof WebInspector.TimelineTabContentView) | |
return new WUI.TimelineTab(view); | |
if (view instanceof WebInspector.ConsoleTabContentView) | |
return new WUI.ConsoleTab(view); | |
if (view instanceof WebInspector.TabBar) | |
return new WUI.TabBar(view); | |
console.assert("Cannot construct wrapper for unsupported View subclass: ", view); | |
return null; | |
} | |
} | |
class WUI.Tab extends WUI.ViewProxy | |
{ | |
get tabBarItem() | |
{ | |
return WUI.ControlProxy.fromObject(this.unwrap().tabBarItem); | |
} | |
} | |
WUI.Tab.Type = { | |
Timeline: Symbol("Tab.Type.Timeline"), | |
Console: Symbol("Tab.Type.Console"), | |
// ... | |
} | |
class WUI.TimelineTab extends WUI.ViewProxy | |
{ | |
// Represents the entire Timeline tab, including the overview, timelines, and their detail views. | |
get type() { return WUI.Tab.Type.Timeline; } | |
} | |
class WUI.TabBarItem extends WUI.ControlProxy | |
{ | |
locate() | |
{ | |
return WUI.ElementProxy.fromElement(this.unwrap().element); | |
} | |
} | |
class WUI.TabBar extends WUI.ViewProxy | |
{ | |
// Class-specific functionality for tests. | |
get newTabItem() { return WUI.ControlProxy.fromObject(return this.unwrap().newTabItem); } | |
get selectedTabItem() { return WUI.ControlProxy.fromObject(this.unwrap().selectedTabBarItem); } | |
get activeTabItems() { return WUI.ControlProxy.fromObjects(this.unwrap().tabBarItems); } | |
get activeTab() { return WUI.ViewProxy.fromView(this.selectedTabItem.unwrap().representedObject); } | |
get activeTabTypes() | |
{ | |
let tabViewTypes = this.unwrap().tabBarItems.map((tabItem) => tabItem.representedObject.type); | |
let tabTypes = tabViewTypes.map((tabViewType) => { | |
switch (tabViewType) { | |
case WebInspector.ConsoleTabContentView.Type: | |
return WUI.Tab.Type.Console; | |
case WebInspector.TimelineTabContentView.Type: | |
return WUI.Tab.Type.Timeline; | |
default: | |
return null; | |
} | |
}); | |
return tabTypes.filter((type) => !!type); | |
} | |
set activeTabTypes(value) | |
{ | |
// TODO. Destructively reset which tabs are in the tab bar. | |
// Test shouldn't assume any particular tab becomes selected. | |
} | |
} | |
class WUI.Timeline extends WUI.ViewProxy { | |
get overviewLabel() | |
{ | |
return WUI.ElementProxy(() => { | |
// FIXME: this is not quite correct, needs more gymnastics. | |
// Or we could be lazy and use a dynamic CSS query to find the tree element <li>. | |
// Right now I am trying to not do such things since it will fail silently. | |
let timeline = this.unwrap().representedObject; | |
let recordingView = this.unwrap().contentBrowser.currentContentView; | |
// FIXME: make these members public, or have some API to get this. | |
return recordingView._timelineOverview._treeElementsByTypeMap.get(timeline.type)._listItemNode; | |
}); | |
} | |
} | |
WUI.Timeline.Type = { | |
Memory: Symbol("Timeline.Type.Memory"), | |
Allocations: Symbol("Timeline.Type.Allocations"), | |
// ... | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment