Skip to content

Commit

Permalink
Merge pull request #47 from DrMarcII/stream_sync
Browse files Browse the repository at this point in the history
Generalize the syncing support for async WebDriver.
  • Loading branch information
DrMarcII committed Jul 20, 2015
2 parents faf8f48 + 331ee20 commit 3beb5c2
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 96 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
.packages
packages
pubspec.lock
.project
Expand Down
78 changes: 35 additions & 43 deletions dart/lib/async/html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import 'src/core.dart';
import 'src/interfaces.dart';
export 'src/interfaces.dart';

typedef Future SyncActionFn();

/// execute [fn] as a separate microtask and return a [Future] that completes
// normally when that [Future] completes (normally or with an error).
Future _microtask(fn()) => new Future.microtask(fn).whenComplete(() {});
Expand All @@ -40,10 +38,9 @@ class HtmlPageLoader extends BasePageLoader {
@override
_HtmlMouse get mouse => _mouse;

final SyncActionFn sync;

HtmlPageLoader(Node globalContext, this.sync, {bool useShadowDom: true})
: super(useShadowDom: useShadowDom) {
HtmlPageLoader(Node globalContext, {bool useShadowDom: true,
SyncedExecutionFn executeSyncedFn: noOpExecuteSyncedFn})
: super(useShadowDom: useShadowDom, executeSyncedFn: executeSyncedFn) {
this._globalContext = new HtmlPageLoaderElement(globalContext, this);
this._mouse = new _HtmlMouse(this);
}
Expand All @@ -70,25 +67,24 @@ class _HtmlMouse implements PageLoaderMouse {
_HtmlMouse(this.loader);

@override
Future down(int button, {_ElementPageLoaderElement eventTarget}) async {
await dispatchEvent('mousedown', eventTarget, button);
await loader.sync();
}
Future down(int button,
{_ElementPageLoaderElement eventTarget, bool sync: true}) => loader
.executeSynced(
() => dispatchEvent('mousedown', eventTarget, button), sync);

@override
Future moveTo(_ElementPageLoaderElement element, int xOffset, int yOffset,
{_ElementPageLoaderElement eventTarget}) async {
{_ElementPageLoaderElement eventTarget, bool sync: true}) => loader
.executeSynced(() {
clientX = (element.node.getBoundingClientRect().left + xOffset).ceil();
clientY = (element.node.getBoundingClientRect().top + yOffset).ceil();
await dispatchEvent('mousemove', eventTarget);
await loader.sync();
}
return dispatchEvent('mousemove', eventTarget);
}, sync);

@override
Future up(int button, {_ElementPageLoaderElement eventTarget}) async {
await dispatchEvent('mouseup', eventTarget);
await loader.sync();
}
Future up(int button,
{_ElementPageLoaderElement eventTarget, bool sync: true}) =>
loader.executeSynced(() => dispatchEvent('mouseup', eventTarget), sync);

int get pageX => window.pageXOffset + clientX;
int get pageY => window.pageYOffset + clientY;
Expand Down Expand Up @@ -180,10 +176,8 @@ abstract class HtmlPageLoaderElement implements PageLoaderElement {
@override
String toString() => '$runtimeType<$node>';

Future type(String keys) async {
await _fireKeyPressEvents(node, keys);
await loader.sync();
}
Future type(String keys, {bool sync: true}) =>
loader.executeSynced(() => _fireKeyPressEvents(node, keys), sync);

// This doesn't work in Dartium due to:
// https://code.google.com/p/dart/issues/detail?id=13902
Expand All @@ -201,11 +195,11 @@ abstract class HtmlPageLoaderElement implements PageLoaderElement {
Stream<String> get classes async* {}

@override
Future clear() async =>
Future clear({bool sync: true}) async =>
throw new PageLoaderException('$runtimeType.clear() is unsupported');

@override
Future click() async =>
Future click({bool sync: true}) async =>
throw new PageLoaderException('$runtimeType.click() is unsupported');

@override
Expand Down Expand Up @@ -253,23 +247,22 @@ class _ElementPageLoaderElement extends HtmlPageLoaderElement {
Stream<String> get classes => new Stream.fromIterable(node.classes);

@override
Future click() => capture(() async {
Future click({bool sync: true}) => loader.executeSynced(() {
if (node is OptionElement) {
await _clickOptionElement();
return _clickOptionElement();
} else {
await _microtask(node.click);
return _microtask(node.click);
}
await loader.sync();
});
}, sync);

Future _clickOptionElement() async {
Future _clickOptionElement() {
OptionElement option = node as OptionElement;
option.selected = true;
await _microtask(() => option.dispatchEvent(new Event('change')));
return _microtask(() => option.dispatchEvent(new Event('change')));
}

@override
Future type(String keys) async {
Future type(String keys, {bool sync: true}) => loader.executeSynced(() async {
node.focus();
await _fireKeyPressEvents(node, keys);
if (node is InputElement || node is TextAreaElement) {
Expand All @@ -280,22 +273,22 @@ class _ElementPageLoaderElement extends HtmlPageLoaderElement {
await _microtask(
() => node.dispatchEvent(new TextEvent('textInput', data: value)));
}
await _microtask(() => node.blur());
await loader.sync();
}
return _microtask(() => node.blur());
}, sync);

@override
Future clear() async {
Future clear({bool sync: true}) => loader.executeSynced(() async {
if (node is InputElement || node is TextAreaElement) {
var node = this.node;
node.focus();
node.value = '';
await _microtask(
() => node.dispatchEvent(new TextEvent('textInput', data: '')));
return _microtask(() => node.blur());
} else {
throw new PageLoaderException('$this does not support clear.');
}
await loader.sync();
}
}, sync);
}

class _ShadowRootPageLoaderElement extends HtmlPageLoaderElement {
Expand All @@ -322,14 +315,13 @@ class _DocumentPageLoaderElement extends HtmlPageLoaderElement {
Future<bool> get displayed async => true;

@override
Future type(String keys) async {
Future type(String keys, {bool sync: true}) => loader.executeSynced(() async {
// TODO(DrMarcII) consider whether this should be sent to
// document.activeElement to more closely match WebDriver behavior.
document.body.focus();
_fireKeyPressEvents(document.body, keys);
document.body.blur();
await loader.sync();
}
await _fireKeyPressEvents(document.body, keys);
return _microtask(() => document.body.blur());
}, sync);
}

class _ElementAttributes extends PageLoaderAttributes {
Expand Down
15 changes: 14 additions & 1 deletion dart/lib/async/src/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,17 @@ Future capture(callback()) {
return completer.future;
}

Future noOpExecuteSyncedFn(Future fn()) => fn();

/// Mechanism for specifying hierarchical page objects using annotations on
/// fields in simple Dart objects.
abstract class BasePageLoader implements PageLoader {
final bool useShadowDom;
final SyncedExecutionFn executeSyncedFn;
int i = 0;

BasePageLoader({this.useShadowDom: true});
BasePageLoader(
{this.useShadowDom: true, this.executeSyncedFn: noOpExecuteSyncedFn});

/// Creates a new instance of [type] and binds annotated fields to
/// corresponding [PageLoaderElement]s.
Expand All @@ -62,6 +67,14 @@ abstract class BasePageLoader implements PageLoader {
ClassMirror type, PageLoaderElement context, bool displayCheck) =>
capture(
() => new _ClassInfo(type).getInstance(context, this, displayCheck));

Future executeSynced(Future fn(), bool sync) {
if (sync) {
return executeSyncedFn(fn);
} else {
return fn();
}
}
}

typedef Future<T> _LazyFunction<T>();
Expand Down
15 changes: 9 additions & 6 deletions dart/lib/async/src/interfaces.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ library pageloader.async.interfaces;

import 'dart:async';

typedef Future<T> SyncedExecutionFn<T>(Future<T> fn());

abstract class Lazy<T> {
Future<T> call();
}
Expand All @@ -33,16 +35,17 @@ abstract class PageLoaderMouse {
/// specified, PageLoader will attempt to fire the corresponding mouse events
/// on that target, otherwise it will fire the events on the target that is
/// under the current mouse location.
Future down(int button, {PageLoaderElement eventTarget});
Future down(int button, {PageLoaderElement eventTarget, bool sync: true});

/// Release [button] on the mouse at its current location. If [eventTarget] is
/// specified, PageLoader will attempt to fire the corresponding mouse events
/// on that target, otherwise it will fire the events on the target that is
/// under the current mouse location.
Future up(int button, {PageLoaderElement eventTarget});
Future up(int button, {PageLoaderElement eventTarget, bool sync: true});

/// Move the mouse to a location relative to [element].
Future moveTo(PageLoaderElement element, int xOffset, int yOffset);
Future moveTo(PageLoaderElement element, int xOffset, int yOffset,
{bool sync: true});
}

abstract class PageLoaderElement {
Expand All @@ -60,9 +63,9 @@ abstract class PageLoaderElement {

Stream<PageLoaderElement> getElementsByCss(String selector);

Future clear();
Future click();
Future type(String keys);
Future clear({bool sync: true});
Future click({bool sync: true});
Future type(String keys, {bool sync: true});
}

abstract class PageLoaderAttributes {
Expand Down
70 changes: 39 additions & 31 deletions dart/lib/async/webdriver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ import 'src/interfaces.dart';
export 'src/interfaces.dart';

class WebDriverPageLoader extends BasePageLoader {
final wd.WebDriver driver;
WebDriverPageLoaderElement _globalContext;
var _mouse;
@override
final _WebDriverMouse mouse;
_WebDriverMouse get mouse => _mouse;

WebDriverPageLoader(wd.SearchContext globalContext, {useShadowDom: true})
: super(useShadowDom: useShadowDom),
this.mouse = new _WebDriverMouse(globalContext.driver) {
WebDriverPageLoader(wd.SearchContext globalContext, {bool useShadowDom: true,
SyncedExecutionFn executeSyncedFn: noOpExecuteSyncedFn})
: this.driver = globalContext.driver,
super(useShadowDom: useShadowDom, executeSyncedFn: executeSyncedFn) {
this._mouse = new _WebDriverMouse(this);
this._globalContext = new WebDriverPageLoaderElement(globalContext, this);
}

Expand All @@ -51,41 +55,42 @@ class WebDriverPageLoader extends BasePageLoader {
}

class _WebDriverMouse implements PageLoaderMouse {
final wd.WebDriver driver;
final WebDriverPageLoader loader;
wd.WebDriver get driver => loader.driver;

_WebDriverMouse(this.driver);
_WebDriverMouse(this.loader);

@override
Future down(int button, {_WebElementPageLoaderElement eventTarget}) async {
Future down(int button,
{_WebElementPageLoaderElement eventTarget, bool sync: true}) => loader
.executeSynced(() {
if (eventTarget == null) {
await driver.mouse.down(button);
return driver.mouse.down(button);
} else {
await _fireEvent(eventTarget, 'mousedown', button);
return _fireEvent(eventTarget, 'mousedown', button);
}
}
}, sync);

@override
Future moveTo(
_WebElementPageLoaderElement element, int xOffset, int yOffset) async {
await driver.mouse.moveTo(
element: element.context, xOffset: xOffset, yOffset: yOffset);
}
Future moveTo(_WebElementPageLoaderElement element, int xOffset, int yOffset,
{bool sync: true}) => loader.executeSynced(() => driver.mouse.moveTo(
element: element.context, xOffset: xOffset, yOffset: yOffset), sync);

@override
Future up(int button, {_WebElementPageLoaderElement eventTarget}) async {
Future up(int button,
{_WebElementPageLoaderElement eventTarget, bool sync: true}) => loader
.executeSynced(() {
if (eventTarget == null) {
await driver.mouse.up(button);
return driver.mouse.up(button);
} else {
await _fireEvent(eventTarget, 'mouseup', button);
return _fireEvent(eventTarget, 'mouseup', button);
}
}
}, sync);

Future _fireEvent(
_WebElementPageLoaderElement eventTarget, String type, int button) async {
await driver.execute(
"arguments[0].dispatchEvent(new MouseEvent(arguments[1], "
"{'button' : arguments[2]}));", [eventTarget.context, type, button]);
}
_WebElementPageLoaderElement eventTarget, String type, int button) =>
driver.execute("arguments[0].dispatchEvent(new MouseEvent(arguments[1], "
"{'button' : arguments[2]}));", [eventTarget.context, type, button]);
}

abstract class WebDriverPageLoaderElement implements PageLoaderElement {
Expand Down Expand Up @@ -133,15 +138,15 @@ abstract class WebDriverPageLoaderElement implements PageLoaderElement {
Stream<String> get classes async* {}

@override
Future clear() async =>
Future clear({bool sync: true}) async =>
throw new PageLoaderException('$runtimeType.clear() is unsupported');

@override
Future click() async =>
Future click({bool sync: true}) async =>
throw new PageLoaderException('$runtimeType.click() is unsupported');

@override
Future type(String keys) async =>
Future type(String keys, {bool sync: true}) async =>
throw new PageLoaderException('$runtimeType.type() is unsupported');

@override
Expand Down Expand Up @@ -203,11 +208,13 @@ class _WebElementPageLoaderElement extends WebDriverPageLoaderElement {
}

@override
Future clear() => context.clear();
Future clear({bool sync: true}) => loader.executeSynced(context.clear, sync);

@override
Future click() => context.click();
Future click({bool sync: true}) => loader.executeSynced(context.click, sync);
@override
Future type(String keys) => context.sendKeys(keys);
Future type(String keys, {bool sync: true}) =>
loader.executeSynced(() => context.sendKeys(keys), sync);
}

class _WebDriverPageLoaderElement extends WebDriverPageLoaderElement {
Expand All @@ -219,7 +226,8 @@ class _WebDriverPageLoaderElement extends WebDriverPageLoaderElement {
@override
Future<String> get name async => '__document__';
@override
Future type(String keys) => context.keyboard.sendKeys(keys);
Future type(String keys, {bool sync: true}) =>
loader.executeSynced(() => context.keyboard.sendKeys(keys), sync);
@override
Future<bool> get displayed async => true;

Expand Down
8 changes: 2 additions & 6 deletions dart/lib/sync/src/annotations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,12 @@ class InShadowDom implements Finder {
var buffer = new StringBuffer('@InShadowDom(');
bool commaNeeded = false;
if (of != null) {
buffer
..write('of: ')
..write(of);
buffer..write('of: ')..write(of);
commaNeeded = true;
}
if (find != null) {
if (commaNeeded) buffer.write(', ');
buffer
..write('find: ')
..write(find);
buffer..write('find: ')..write(find);
}
buffer.write(')');
return buffer.toString();
Expand Down
Loading

0 comments on commit 3beb5c2

Please sign in to comment.