Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize the syncing support for async WebDriver. #47

Merged
merged 1 commit into from
Jul 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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