pageloader 3.3.0

  • Readme
  • Changelog
  • Installing
  • 80

PageLoader #

Build Status

A framework for creating page objects for in-browser tests or Webdriver tests.

Starting with version 3.0.0, PageLoader is changed significantly from version 2.X.X. Refer to "What Changed since version 2?" section for more details.

A strongly typed implementation of PageLoader for Dart and does not depend on dart:mirrors, which is slated for removal in Dart 2.0. If you're starting fresh with Dart UI testing, we recommend you use PageLoader version 3.X.X. PageLoader version 2.X.X and below is deprecated and no longer supported.

Migration guide will be added shortly.

If you are looking for the legacy version of PageLoader (with mirrors), refer to the branch "2-stable" which is in sync with version 2.2.7.

What Changed since version 2? #

There are three big changes here:

  1. Instead of using dart:mirrors, code generation is used. Unfortunately this means extra boilerplate code. Every PageObject you write will also now have a generated component.

  2. Everything is lazily loaded by default. Lazy<T> and @optional from version 2.X.X no longer exists.

  3. Sync-ification of the API, which means significantly fewer Futures and awaits.

Boilerplate #

Version 3.0.0 and above uses Dart code-gen, so the generator needs a couple of hints to trigger the generation step. The boilerplate is:

// Assume this file is called: 'my_po.dart'
import 'package:pageloader/pageloader.dart';

part 'my_po.g.dart';

abstract class MyPO {
  factory MyPO.create(PageLoaderElement context) =

PageObject containing Dart files must be in test/... directory for the code generation step to occur.

For example:

  1. test/page_objects/special_po.dart will work
  2. test/src/page_objects/special_po.dart will work
  3. lib/src/foo/my_po.dart will not work
    • File is located in lib/... directory; must be in test/...

The above restriction is only temporary and will be relaxed in the future. But by convention, this should be done even after the requirement is relaxed.

Above is the bare minimum boilerplate code needed for a PageObject. Feel free to cut/paste this when starting new page objects.

To construct a PageObject, use this create constructor on either HtmlPageLoaderElement or WebdriverPageLoaderElement.

HtmlPageLoaderElement example: #

import 'package:pageloader/html.dart';

Element myElement = ...;
final context = HtmlPageLoaderElement.createFromElement(myElement);
final myPO = MyPO.create(context);

createFromElement has an additional named argument SyncFn externalSyncFn. This synchronizing function is called on asynchronous events (click, type, etc.) and ensures that these events have time to take into effect. By default, this is a no-op function.

An example of a custom sync function:

        externalSyncFn: (Future action()) async {
      await action();
      // Wait longer than normal
      for (var i = 0; i < 1000; i++) {
        await Future.value();

Note that this externalSyncFn is then called on every asynchronous method of that HtmlPageLoaderElement as well as its childrens' HtmlPageLoaderElements. Refer to "What is synchronous/asynchronous" section for more information about which events are asynchronous.

WebdriverPageLoaderElement example: #

import 'package:pageloader/webdriver.dart';
import 'package:webdriver/sync_io.dart';

String pagePath = ...; // Page uri path
Webdriver driver = ...; // Refer to Webdriver package documentation
WebDriverPageUtils loader = WebDriverPageUtils(driver);

WebDriverPageLoaderElement context = loader.root;
WebDriverMouse get mouse = loader.mouse;

final myPO = MyPO.create(context);

// tests...

loader = null;

How do I trigger the generation step? #

pub run build_runner build

If you are starting with a fresh checkout or deleted your .dart_tool directory, pass the flag: --delete-conflicting-outputs.

Lazy Loading #

Starting from version 3, all elements are lazy.

final myPO = MyPO.create(pageLoaderElementContext);

Nothing happens with the browser at this point. You need to either read information from the page or interact with the browser:

final anotherPO = myPO.anotherPO; // Still does nothing; // Finds the page object, and clicks it
expect(anotherPO.innertext, 'text'); // Finds page object again and reads inner text

This matches how the rest of the API worked: you read attributes lazily, you read text lazily, and now you find elements lazily.

Existence Checking #

In PageLoader version 2.X.X, the @optional tag was used to mark some entity as possibly not existing. For example:

// Pageloader2
class MyPO {
  PageLoaderElement someElement;
  SomePO somePO;

In the case that either someElement or somePO did not exist, it would have null value.

Starting from version 3, @optional is removed and these entities no longer return as null. For PageLoaderElement, you directly use its .exists getter to check or use provided matchers.

import 'package:pageloader/testing.dart';

PageLoaderElement myElement = ...;
expect(myElement.exists, isTrue);
expect(myElement, exists);

For PageObjects, use provided matchers:

import 'package:pageloader/testing.dart';

SomePO somePO = SomePO.create(context);
expect(somePO, exists);

What is synchronous/asynchronous? #

PageLoaderElements in version 3.0.0 and above use both synchronous and asynchronous calls in the API. The basic rule is as follows: any call that cannot change the DOM is synchronous, and any call that can change the DOM is asynchronous.

For example, reading DOM attributes or finding elements is synchronous. Click and typing are still asynchronous.

Future<bool> doSomething() async {
  final text = myPageElement.innerText;
  await myOtherElement.type(text);
  return myOtherElement.attributes['someattr'] == 'value';

Why do we still have async methods? #

Interactions, e.g. clicking, typing, etc. still return Futures. Why?

Remember that component tests actually run in the browser, with the component, in the same thread. Dart (like JavaScript) has no threads, so if the test is doing something then the component is not. If the whole test is synchronous it'll execute start to finish without any pause, as one massive action in the event loop.

So, we'll type something, but the component won't update... because it can't actually execute. And the next line, where we assert something about the component it will fail:

expect(myPo.someElement.innerText, 'someKeys'); // This won't work!

We need to allow the component to update. Having interactions with the browser (i.e. clicking, typing) be asynchronous allows the component to change:

await myPo.type('someKeys'); // Generates a Future.
expect(myPo.someElement.innerText, 'someKeys'); // This is fine now.

Inheritance #

With code-gen, direct inheritance is no longer allowed. However, you can use mixins or delegation to keep 'inheritance-like' behavior in your code.


abstract class BasePO {
  // ... boilerplate code ...
  MyWidgetPO get myWidget;
  String get widgetInnerText => myWidget.innerText;

abstract class ExtraPO extends BasePO { // 'extends' not allowed
  // ... boilerplate code ...
  MyExtraWidgetPO get extraWidget;

Delegation method fix:

abstract class BasePO {
  // ... boilerplate code ...
  MyWidgetPO get myWidget;
  String get widgetInnerText => myWidget.innerText;

abstract class ExtraPO {
  // ... boilerplate code ...
  BasePO get _basePO;
  MyWidgetPO get myWidget => _basePO.myWidget;
  String get widgetInnerText => _basePO.widgetInnerText;
  MyExtraWidgetPO get extraWidget;

Mixin method fix:

abstract class BasePO extends Object with BasePOMixin {
  // This should ONLY have the constructors
  factory BasePO.create(PageLoaderElement context) =

abstract class BasePOMixin {
  // Note that here are no constructors here.
  MyWidgetPO get myWidget;
  String get widgetInnerText => myWidget.innerText;

abstract class ExtraPO extends Object with BasePOMixin {
  // ... boilerplate code ...
  // No need to define 'myWidget' and 'widgetInnerText' here.
  // The mixin will handle these.
  MyExtraWidgetPO get extraWidget;

3.3.0 #

  • Expose more files within lib/html.dart and lib/webdriver.dart.
  • New PageLoaderPointer entity which behaves similarly to PageLoaderMouse. Only supported for HTML at the moment.
  • Add @Pointer annotation (similar to @Mouse).
  • Fix PageLoader checks when using @CheckTag(...) for Safari browser - which produces all upper case tag names.
  • Numerous Dart lint fixes and hiding lints within generated files.
  • Add ClickOption parameter to click-based events within PageLoaderElement.
  • Add scroll() and scrollIntoView() functionality to PageLoaderElement.
  • focused and pointer getters added to `PageUtils.

3.2.5 #

  • Use built_value version 7.0.0.
  • Re-add lint ignores in generated files.

3.2.4 #

  • Bump SDK minimum version to 2.3.

3.2.3 #

  • Bump analyzer version to up to 0.40.0.

3.2.2 #

  • build_config bumped to 1.6.0.
  • Allow analyzer version 0.37.x.

3.2.1 #

  • build_config dependency now uses version range.

3.2.0 #

  • Allow analyzer version 0.36.x.
  • Require Dart SDK >=2.2.0.

3.1.1 #

  • Generated tagName getter is now const if valid, getter if not valid.
  • HtmlPageLoaderElement's typeSequence now calls syncFn.

3.1.0 #

  • Added @nullElement annotation to create a non-existent PageLoaderElement object. Null elements should be returned instead of null for better compatibility with utils and matcher.
  • (HtmlPageLoaderElement only) type(...) better emulates individual keystrokes and sends keyUp, keyPress and keyDown more accurately.
  • (HtmlPageLoaderElement only) typeSequence(...) added to better emulate sequence of individual keystrokes.
  • isVisible utility function and matcher to determine whether an element exists, isDisplayed, and isNotHidden.
  • Removed @DisplayedOnly. Use only @IsDisplayed instead.
  • (HtmlPageLoaderElement only) Supports keyboard input into elements with contenteditable attribute.
  • More descriptive error messages.
  • Lint and deprecated artifact clean up.

3.0.3 #

  • Support the latest release of package:built_value_generator and package:analyzer.

3.0.2 #

  • Support the latest release of package:built_value, package:quiver, and package:source_gen.

3.0.1 #

  • Support the latest release of package:build.

3.0.0 #

  • Added lookup constructor that can also be delegated as another constructor.
  • Improved HtmlMouse to send more accurate mouse events. moveTo API changed.
  • Added clickOutside() API to PageLoaderElement.
  • Lint fixes and suppression in generated code.
  • Remove unnecessary usages of new and const.
  • Bump webdriver to v2.0.0.
  • Update to the latest source_gen. This generator can now be used with other generators that want to write to .g.dart files without a manual build script.
  • Breaking: The header builder option is no longer supported.
  • @EnsureTag is marked as deprecated. Will be removed in a future release.
  • Add listeners into WebdriverPageLoaderElement if searching for descendants.
  • Add @ByCheckTag() as a convenience annotation that can be used in place of @ByTagName(...).
  • Dart 2 compatible version that uses code generation instead of mirrors.
  • Dropped @FirstByCss() annotation class. Use @First(ByCss(...)).
  • Added matchers for PageLoaderElement and PageObjects.

2.2.6 #

  • Fix static analysis warnings for Dart and DDC 1.23.
  • Remove PageLoaderElement.attributes, and class WithAttribute.
  • Add properties and attributes fields to PageLoaderElement.
  • Deprecate PageLoaderElement.seleniumAttributes.

2.2.5 #

  • Bump minimum SDK version to 1.21.0.
  • Pageloader now builds and tests with Bazel.
  • Add new seleniumAttributes getter to PageLoaderElement.

2.2.4 #

  • Remove broken KeyEvent use
  • Added docs to PageLoaderElement

2.2.3 #

  • remove unnecessary casts
  • Make getInstance a generic method

2.2.2 #

  • strong mode fixes
  • added changelog

2.2.1 #

  • Fix more strong mode errors.

2.2.0 #

  • Fix some strong mode errors.

2.1.2 #

  • Add FirstByCss finder to help support recursively defined components.

2.0.2 #

  • Fixed compatibility issue with the webdriver package's awaitChecking.

2.0.1 #

  • No functional change. Test Setup has been refactored to eliminate code duplication.

2.0.0 #

  • No functional change, just bumping the version number.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:

  pageloader: ^3.3.0

2. Install it

You can install packages from the command line:

with pub:

$ pub get

with Flutter:

$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:

import 'package:pageloader/pageloader.dart';
Describes how popular the package is relative to other packages. [more]
Code health derived from static analysis. [more]
Reflects how tidy and up-to-date the package is. [more]
Weighted score of the above. [more]
Learn more about scoring.

We analyzed this package on Jul 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.13

Health suggestions

Fix lib/src/generators/pageobject_generator.dart. (-1.49 points)

Analysis of lib/src/generators/pageobject_generator.dart reported 3 hints:

line 234 col 39: 'isObject' is deprecated and shouldn't be used. Use isDartCoreObject.

line 236 col 27: 'displayName' is deprecated and shouldn't be used. Use getDisplayString instead.

line 246 col 24: 'displayName' is deprecated and shouldn't be used. Use getDisplayString instead.

Fix lib/src/generators/methods/annotation_evaluators.dart. (-1 points)

Analysis of lib/src/generators/methods/annotation_evaluators.dart reported 2 hints:

line 54 col 37: 'type' is deprecated and shouldn't be used.

line 57 col 20: 'type' is deprecated and shouldn't be used.

Fix lib/src/generators/methods/core.dart. (-0.50 points)

Analysis of lib/src/generators/methods/core.dart reported 1 hint:

line 171 col 13: 'name' is deprecated and shouldn't be used. Check element, or use getDisplayString().

Maintenance suggestions

Maintain an example. (-10 points)

Create a short demo in the example/ directory to show how to use this package.

Common filename patterns include main.dart, example.dart, and pageloader.dart. Packages with multiple examples should provide example/

For more information see the pub package layout conventions.


Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.3.0 <3.0.0
analyzer >=0.36.0 <0.40.0 0.39.12
build >0.12.7 <2.0.0 1.3.0
build_config >=0.3.1 <5.0.0 0.4.2
built_value ^7.0.0 7.1.0
matcher ^0.12.0+1 0.12.8
quiver >=2.0.0 <3.0.0 2.1.3
source_gen ^0.9.1+3 0.9.5
webdriver ^2.1.0 2.1.2
Transitive dependencies
_fe_analyzer_shared 5.0.0
archive 2.0.13
args 1.6.0
async 2.4.2
built_collection 4.3.2
charcode 1.1.3
checked_yaml 1.0.2
collection 1.14.13
convert 2.1.1
crypto 2.1.5
csslib 0.16.1
dart_style 1.3.6
fixnum 0.10.11
glob 1.2.0
html 0.14.0+3
js 0.6.2
json_annotation 3.0.1
logging 0.11.4
meta 1.2.1
node_interop 1.1.1
node_io 1.1.1
package_config 1.9.3
pedantic 1.9.1
pub_semver 1.4.4
pubspec_parse 0.1.5
source_span 1.7.0
stack_trace 1.9.5
string_scanner 1.0.5
sync_http 0.2.0
term_glyph 1.1.0
typed_data 1.2.0
watcher 0.9.7+15
yaml 2.2.1
Dev dependencies
build_runner ^1.6.0
built_value_generator ^7.0.0
path ^1.3.6 1.7.0
test ^1.2.0