diff --git a/.gitignore b/.gitignore index 4b8459b6..969b311e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store +.packages packages pubspec.lock .project diff --git a/dart/lib/async/html.dart b/dart/lib/async/html.dart index 485de8a3..1dcf009f 100644 --- a/dart/lib/async/html.dart +++ b/dart/lib/async/html.dart @@ -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(() {}); @@ -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); } @@ -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; @@ -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 @@ -201,11 +195,11 @@ abstract class HtmlPageLoaderElement implements PageLoaderElement { Stream 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 @@ -253,23 +247,22 @@ class _ElementPageLoaderElement extends HtmlPageLoaderElement { Stream 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) { @@ -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 { @@ -322,14 +315,13 @@ class _DocumentPageLoaderElement extends HtmlPageLoaderElement { Future 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 { diff --git a/dart/lib/async/src/core.dart b/dart/lib/async/src/core.dart index eba0bea0..7630cfe6 100644 --- a/dart/lib/async/src/core.dart +++ b/dart/lib/async/src/core.dart @@ -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. @@ -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 _LazyFunction(); diff --git a/dart/lib/async/src/interfaces.dart b/dart/lib/async/src/interfaces.dart index 5e96501f..9ae77771 100644 --- a/dart/lib/async/src/interfaces.dart +++ b/dart/lib/async/src/interfaces.dart @@ -15,6 +15,8 @@ library pageloader.async.interfaces; import 'dart:async'; +typedef Future SyncedExecutionFn(Future fn()); + abstract class Lazy { Future call(); } @@ -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 { @@ -60,9 +63,9 @@ abstract class PageLoaderElement { Stream 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 { diff --git a/dart/lib/async/webdriver.dart b/dart/lib/async/webdriver.dart index b06aa5b6..9f72b46b 100644 --- a/dart/lib/async/webdriver.dart +++ b/dart/lib/async/webdriver.dart @@ -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); } @@ -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 { @@ -133,15 +138,15 @@ abstract class WebDriverPageLoaderElement implements PageLoaderElement { Stream 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 @@ -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 { @@ -219,7 +226,8 @@ class _WebDriverPageLoaderElement extends WebDriverPageLoaderElement { @override Future 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 get displayed async => true; diff --git a/dart/lib/sync/src/annotations.dart b/dart/lib/sync/src/annotations.dart index efb72e21..6532c4ee 100644 --- a/dart/lib/sync/src/annotations.dart +++ b/dart/lib/sync/src/annotations.dart @@ -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(); diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml index 0618176d..99f4f668 100644 --- a/dart/pubspec.yaml +++ b/dart/pubspec.yaml @@ -1,17 +1,17 @@ name: pageloader -version: 2.0.0-pre.5+1 +version: 2.0.0-pre.6 author: Marc Fisher II description: > Supports the creation of page objects that can be shared between in-browser tests and WebDriver tests. homepage: https://github.com/google/pageloader environment: - sdk: '>=1.10.0 <2.0.0' + sdk: '>=1.11.0 <2.0.0' dependencies: matcher: '^0.12.0+1' stack_trace: '^1.3.4' sync_webdriver: '^2.0.0-pre.0' - webdriver: '^0.10.0-pre.11' + webdriver: '^0.10.0-pre.12' dev_dependencies: - path: '^1.3.5' - test: '^0.12.3+3' + path: '^1.3.6' + test: '^0.12.3+7' diff --git a/dart/test/async/html_no_shadow_dom_test.dart b/dart/test/async/html_no_shadow_dom_test.dart index 05e56c9b..f70c7341 100644 --- a/dart/test/async/html_no_shadow_dom_test.dart +++ b/dart/test/async/html_no_shadow_dom_test.dart @@ -28,13 +28,16 @@ import 'src/shared.dart' as shared; void main() { setUp(() { var div = html_setup.setUp(); - shared.loader = new HtmlPageLoader(div, syncFn, useShadowDom: false); + shared.loader = + new HtmlPageLoader(div, executeSyncedFn: syncFn, useShadowDom: false); }); plt.runTests(); html_test.runTests(); } -Future syncFn() async { +syncFn(fn) async { + var value = await fn(); await new Future.delayed(new Duration(milliseconds: 200)); + return value; } diff --git a/dart/test/async/html_test.dart b/dart/test/async/html_test.dart index 3ceffda8..221c57fa 100644 --- a/dart/test/async/html_test.dart +++ b/dart/test/async/html_test.dart @@ -27,13 +27,15 @@ import 'src/shared.dart' as shared; void main() { setUp(() { var div = html_setup.setUp(); - shared.loader = new HtmlPageLoader(div, syncFn); + shared.loader = new HtmlPageLoader(div, executeSyncedFn: syncFn); }); plt.runTests(); html_test.runTests(); } -Future syncFn() async { +syncFn(fn) async { + var value = await fn(); await new Future.delayed(new Duration(milliseconds: 200)); + return value; }