├── test
├── fixtures
│ ├── paths
│ │ ├── bar
│ │ │ ├── a.js
│ │ │ ├── index.js
│ │ │ └── index.html
│ │ ├── baz
│ │ │ ├── b.js
│ │ │ ├── a.html
│ │ │ ├── b
│ │ │ │ ├── one.js
│ │ │ │ ├── index.html
│ │ │ │ └── deep
│ │ │ │ │ ├── index.html
│ │ │ │ │ ├── stuff.html
│ │ │ │ │ └── stuff.js
│ │ │ └── a
│ │ │ │ └── fizz.html
│ │ ├── foo.html
│ │ ├── foo.js
│ │ └── foo
│ │ │ ├── one.js
│ │ │ ├── three.css
│ │ │ └── two.html
│ ├── cli
│ │ ├── conf
│ │ │ ├── test
│ │ │ │ └── foo.js
│ │ │ ├── branch
│ │ │ │ └── leaf
│ │ │ │ │ └── thing.js
│ │ │ ├── wct.conf.js
│ │ │ ├── json
│ │ │ │ ├── wct.conf.json
│ │ │ │ └── wct.conf.js
│ │ │ └── rooted
│ │ │ │ └── wct.conf.js
│ │ └── standard
│ │ │ ├── test
│ │ │ ├── a.html
│ │ │ └── b.js
│ │ │ └── x-foo.html
│ ├── integration
│ │ ├── nested
│ │ │ ├── test
│ │ │ │ ├── leaf.js
│ │ │ │ ├── leaf.html
│ │ │ │ ├── one
│ │ │ │ │ ├── index.html
│ │ │ │ │ └── tests.html
│ │ │ │ ├── two
│ │ │ │ │ └── index.html
│ │ │ │ └── index.html
│ │ │ └── golden.json
│ │ ├── components_dir
│ │ │ ├── bower_components
│ │ │ │ └── foo-element
│ │ │ │ │ └── foo-element.js
│ │ │ ├── golden.json
│ │ │ └── test
│ │ │ │ └── index.html
│ │ ├── multiple-component_dirs
│ │ │ ├── bower_components-bar
│ │ │ │ └── package
│ │ │ │ │ └── index.js
│ │ │ ├── bower_components-foo
│ │ │ │ └── package
│ │ │ │ │ └── index.js
│ │ │ ├── bower_components
│ │ │ │ └── package
│ │ │ │ │ └── index.js
│ │ │ ├── test
│ │ │ │ └── index.html
│ │ │ └── golden.json
│ │ ├── missing
│ │ │ ├── golden.json
│ │ │ └── test
│ │ │ │ └── missing.html
│ │ ├── no-tests
│ │ │ ├── golden.json
│ │ │ └── test
│ │ │ │ └── index.html
│ │ ├── failing
│ │ │ ├── test
│ │ │ │ ├── tests.js
│ │ │ │ ├── tests.html
│ │ │ │ └── index.html
│ │ │ └── golden.json
│ │ ├── hybrid
│ │ │ ├── test
│ │ │ │ ├── tests.js
│ │ │ │ ├── tests.html
│ │ │ │ └── index.html
│ │ │ └── golden.json
│ │ ├── one-js
│ │ │ ├── test
│ │ │ │ ├── tests.js
│ │ │ │ └── index.html
│ │ │ └── golden.json
│ │ ├── many-js
│ │ │ ├── test
│ │ │ │ ├── one.js
│ │ │ │ ├── three.js
│ │ │ │ ├── two.js
│ │ │ │ └── index.html
│ │ │ └── golden.json
│ │ ├── compilation
│ │ │ ├── golden.json
│ │ │ └── test
│ │ │ │ └── index.html
│ │ ├── inline-js
│ │ │ ├── golden.json
│ │ │ └── test
│ │ │ │ └── index.html
│ │ ├── one-html
│ │ │ ├── golden.json
│ │ │ └── test
│ │ │ │ ├── index.html
│ │ │ │ └── tests.html
│ │ ├── define-webserver-hook
│ │ │ ├── test
│ │ │ │ ├── index.html
│ │ │ │ └── tests.html
│ │ │ └── golden.json
│ │ ├── many-html
│ │ │ ├── test
│ │ │ │ ├── index.html
│ │ │ │ ├── one.html
│ │ │ │ ├── two.html
│ │ │ │ └── three.html
│ │ │ └── golden.json
│ │ ├── multiple-replace
│ │ │ ├── projection-element-2.html
│ │ │ ├── projection-element-3.html
│ │ │ ├── projection-element-4.html
│ │ │ ├── exception-element.html
│ │ │ ├── test
│ │ │ │ ├── index.html
│ │ │ │ └── tests.html
│ │ │ ├── normal-element.html
│ │ │ ├── exception-fixture.html
│ │ │ ├── projection-element.html
│ │ │ ├── dom-if-element.html
│ │ │ ├── golden.json
│ │ │ └── dom-repeat-fixture.html
│ │ └── query-string
│ │ │ ├── test
│ │ │ ├── tests.html
│ │ │ ├── index.html
│ │ │ └── tests.js
│ │ │ └── golden.json
│ └── early-failure
│ │ ├── bower_components
│ │ └── web-component-tester
│ │ │ └── package.json
│ │ └── test
│ │ └── index.html
├── unit
│ ├── config.ts
│ ├── gulp.ts
│ ├── paths.ts
│ ├── grunt.ts
│ └── context.ts
└── integration
│ └── setup_test_dir.ts
├── a11ySuiteExample.png
├── .npmignore
├── .clang-format
├── data
├── a11ySuite-npm-header.txt
├── index.html
└── a11ySuite.js
├── custom_typings
├── findup-sync.d.ts
├── server-destroy.d.ts
├── promisify-node.d.ts
├── send.d.ts
├── stacky.d.ts
└── wd.d.ts
├── .github
└── PULL_REQUEST_TEMPLATE
├── .gitignore
├── browser
├── more-declarations.ts
├── tsconfig.json
├── mocha
│ ├── fixture.ts
│ ├── stub.ts
│ ├── extend.ts
│ └── replace.ts
├── environment
│ ├── errors.ts
│ ├── compatability.ts
│ └── helpers.ts
├── declarations.ts
├── reporters
│ ├── html.ts
│ ├── title.ts
│ └── console.ts
├── reporters.ts
├── environment.ts
├── mocha.ts
├── index.ts
├── config.ts
├── clisocket.ts
├── childrunner.ts
└── suites.ts
├── .travis.yml
├── browser-js-header.txt
├── .vscode
└── settings.json
├── runner.js
├── tsconfig.json
├── tasks
└── test.js
├── bin
├── wct
└── wct-st
├── bower.json
├── wct-browser-legacy
├── package.json
└── a11ySuite.js
├── LICENSE
├── runner
├── gulp.ts
├── port-scanner.ts
├── test.ts
├── paths.ts
├── httpbin.ts
├── plugin.ts
├── cli.ts
├── steps.ts
└── context.ts
├── tslint.json
├── package.json
└── gulpfile.js
/test/fixtures/paths/bar/a.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/b.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/foo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/foo.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/bar/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/a.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/b/one.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/foo/one.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/foo/three.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/foo/two.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/cli/conf/test/foo.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/cli/standard/test/a.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/cli/standard/test/b.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/cli/standard/x-foo.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/bar/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/a/fizz.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/b/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/cli/conf/branch/leaf/thing.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/b/deep/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/b/deep/stuff.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/paths/baz/b/deep/stuff.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/test/leaf.js:
--------------------------------------------------------------------------------
1 | test('js test', function() {});
2 |
--------------------------------------------------------------------------------
/a11ySuiteExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/comcast/web-component-tester/HEAD/a11ySuiteExample.png
--------------------------------------------------------------------------------
/test/fixtures/early-failure/bower_components/web-component-tester/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.1"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/integration/components_dir/bower_components/foo-element/foo-element.js:
--------------------------------------------------------------------------------
1 | window.fooElementLoaded = 'yes';
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-component_dirs/bower_components-bar/package/index.js:
--------------------------------------------------------------------------------
1 | window.nameOfThing = 'bar';
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-component_dirs/bower_components-foo/package/index.js:
--------------------------------------------------------------------------------
1 | window.nameOfThing = 'foo';
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-component_dirs/bower_components/package/index.js:
--------------------------------------------------------------------------------
1 | window.nameOfThing = 'mainline';
--------------------------------------------------------------------------------
/test/fixtures/integration/missing/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 0,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "error"
6 | }
--------------------------------------------------------------------------------
/test/fixtures/integration/no-tests/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 0,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete"
6 | }
--------------------------------------------------------------------------------
/test/fixtures/cli/conf/wct.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | sauce: {
4 | username: 'abc123',
5 | },
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/test/fixtures/cli/conf/json/wct.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": "..",
3 | "plugins": {
4 | "sauce": {
5 | "username": "jsonconf"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/integration/failing/test/tests.js:
--------------------------------------------------------------------------------
1 | test('passing test', function() {});
2 | test('failing test', function() {
3 | assert.isTrue(false);
4 | });
5 |
--------------------------------------------------------------------------------
/test/fixtures/integration/hybrid/test/tests.js:
--------------------------------------------------------------------------------
1 | suite('suite', function() {
2 | test('nested test', function() {});
3 | });
4 | test('test', function() {});
5 |
--------------------------------------------------------------------------------
/test/fixtures/integration/one-js/test/tests.js:
--------------------------------------------------------------------------------
1 | suite('suite', function() {
2 | test('nested test', function() {});
3 | });
4 | test('test', function() {});
5 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-js/test/one.js:
--------------------------------------------------------------------------------
1 | suite('suite 1', function() {
2 | test('nested test 1', function() {});
3 | });
4 | test('test 1', function() {});
5 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-js/test/three.js:
--------------------------------------------------------------------------------
1 | suite('suite 3', function() {
2 | test('nested test 3', function() {});
3 | });
4 | test('test 3', function() {});
5 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-js/test/two.js:
--------------------------------------------------------------------------------
1 | suite('suite 2', function() {
2 | test('nested test 2', function() {});
3 | });
4 | test('test 2', function() {});
5 |
--------------------------------------------------------------------------------
/test/fixtures/cli/conf/rooted/wct.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | root: path.resolve(__dirname, '../../..'),
5 | suites: ['cli/conf/test'],
6 | };
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Update .gitignore whenever you update this file!
2 |
3 | .todo
4 | bower_components
5 | node_modules
6 | npm-debug.log
7 | typings/
8 |
9 | # Don't ignore runner/*.js
10 |
--------------------------------------------------------------------------------
/test/fixtures/cli/conf/json/wct.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | root: path.resolve(__dirname, '..'),
5 | plugins: {
6 | sauce: {
7 | username: 'jsconf',
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Google
2 | AllowShortBlocksOnASingleLine: false
3 | AllowShortCaseLabelsOnASingleLine: false
4 | AllowShortFunctionsOnASingleLine: None
5 | AllowShortIfStatementsOnASingleLine: false
6 | AllowShortLoopsOnASingleLine: false
7 |
--------------------------------------------------------------------------------
/test/fixtures/integration/components_dir/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 1,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "inline passing test": {"state": "passing"}
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/test/fixtures/integration/compilation/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 1,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "ES6 works": {
9 | "state": "passing"
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixtures/integration/one-js/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 2,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "suite": {"nested test": {"state": "passing"}},
9 | "test": {"state": "passing"}
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/test/fixtures/integration/inline-js/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 2,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "suite": {"nested test": {"state": "passing"}},
9 | "test": {"state": "passing"}
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/data/a11ySuite-npm-header.txt:
--------------------------------------------------------------------------------
1 | import * as polymerDom from '../@polymer/polymer/lib/legacy/polymer.dom.js';
2 | const Polymer = { dom: polymerDom };
3 | export {a11ySuiteExport as a11ySuite};
4 |
5 | // wct-browser-legacy/a11ySuite.js is a generated file. Source is in web-component-tester/data/a11ySuite.js
6 |
--------------------------------------------------------------------------------
/test/fixtures/integration/one-html/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 2,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/tests.html": {
8 | "suite": {"nested test": {"state": "passing"}},
9 | "test": {"state": "passing"}
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/test/fixtures/integration/no-tests/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/custom_typings/findup-sync.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'findup-sync' {
2 | import * as minimatch from 'minimatch';
3 |
4 | interface IOptions extends minimatch.IOptions {
5 | cwd?: string;
6 | }
7 |
8 | function mod(pattern: string[]|string, opts?: IOptions): string;
9 | namespace mod {}
10 | export = mod;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/test/leaf.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/one-html/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/one-js/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/test/one/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/test/one/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/test/two/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/missing/test/missing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/define-webserver-hook/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-js/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-html/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/projection-element-2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | two
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/projection-element-3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | three
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/projection-element-4.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/early-failure/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/fixtures/integration/define-webserver-hook/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 2,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/tests.html": {
8 | "suite": {
9 | "nested test": {
10 | "state": "passing"
11 | }
12 | },
13 | "test": {
14 | "state": "passing"
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixtures/integration/nested/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 4,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {"js test": {"state": "passing"}},
8 | "test/one/tests.html": {"test": {"state": "passing"}},
9 | "test/two/": {"inline test": {"state": "passing"}},
10 | "test/leaf.html": {"test": {"state": "passing"}}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/exception-element.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
--------------------------------------------------------------------------------
/test/fixtures/integration/query-string/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/fixtures/integration/failing/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/hybrid/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/inline-js/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/one-html/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-html/test/one.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-html/test/two.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-html/test/three.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/define-webserver-hook/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/fixtures/integration/components_dir/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test/fixtures/integration/failing/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/normal-element.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hello world!
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-component_dirs/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/fixtures/integration/hybrid/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-js/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 6,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "suite 1": {"nested test 1": {"state": "passing"}},
9 | "test 1": {"state": "passing"},
10 | "suite 2": {"nested test 2": {"state": "passing"}},
11 | "test 2": {"state": "passing"},
12 | "suite 3": {"nested test 3": {"state": "passing"}},
13 | "test 3": {"state": "passing"}
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 |
15 |
16 | - [ ] CHANGELOG.md has been updated
17 |
--------------------------------------------------------------------------------
/test/fixtures/integration/query-string/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 3,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/tests.html": {
8 | "preserves query strings": {
9 | "state": "passing"
10 | }
11 | },
12 | "test/": {
13 | "preserves query strings (?fizz=buzz&foo=bar)": {
14 | "state": "passing"
15 | },
16 | "preserves query strings (?fizz=buzz)": {
17 | "state": "passing"
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Update .npmignore whenever you update this file!
2 |
3 | .todo
4 | /bower_components
5 | /node_modules
6 | package-lock.json
7 | npm-debug.log
8 | typings/
9 | runner/*.js
10 | runner/*.d.ts
11 | runner/*.js.map
12 | test/unit/*.js
13 | test/unit/*.d.ts
14 | test/unit/*.js.map
15 | test/integration/*.js
16 | test/integration/*.d.ts
17 | test/integration/*.js.map
18 | test/fixtures/integration/temp
19 |
20 | browser/*.js
21 | browser/*.js.map
22 | browser/environment/*.js*
23 | browser/mocha/*.js*
24 | browser/reporters/*.js*
25 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/exception-fixture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/test/fixtures/integration/hybrid/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 6,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "inline suite": {"inline nested test": {"state": "passing"}},
9 | "inline test": {"state": "passing"},
10 | "suite": {"nested test": {"state": "passing"}},
11 | "test": {"state": "passing"}
12 | },
13 | "test/tests.html": {
14 | "suite": {"nested test": {"state": "passing"}},
15 | "test": {"state": "passing"}
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/test/fixtures/integration/query-string/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/fixtures/integration/many-html/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 6,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/one.html": {
8 | "suite 1": {"nested test 1": {"state": "passing"}},
9 | "test 1": {"state": "passing"}
10 | },
11 | "test/two.html": {
12 | "suite 2": {"nested test 2": {"state": "passing"}},
13 | "test 2": {"state": "passing"}
14 | },
15 | "test/three.html": {
16 | "suite 3": {"nested test 3": {"state": "passing"}},
17 | "test 3": {"state": "passing"}
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/test/fixtures/integration/compilation/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/custom_typings/server-destroy.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'server-destroy' {
2 | import * as http from 'http';
3 |
4 | /**
5 | * Monkey-patches the destroy() method onto the given server.
6 | *
7 | * It only accepts DestroyableServers as parameters to remind the user
8 | * to update their type annotations elsewhere, as we can't express the
9 | * mutation in the type system directly.
10 | */
11 | function enableDestroy(server: enableDestroy.DestroyableServer): void;
12 | namespace enableDestroy {
13 | interface DestroyableServer extends http.Server {
14 | destroy(): void;
15 | }
16 | }
17 | export = enableDestroy;
18 | }
19 |
--------------------------------------------------------------------------------
/data/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
12 |
13 | <% extraScripts.forEach(function(script) { %>
14 |
15 | <% }); %>
16 | <% if (typeof npm === 'undefined' || !npm) { %>
17 |
18 | <% } %>
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/custom_typings/promisify-node.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'promisify-node' {
2 | interface NodeCallback {
3 | (err: any, value: T): void;
4 | }
5 | function promisify(f: (cb: NodeCallback) => void): () => Promise;
6 | function promisify(f: (a: A1, cb: NodeCallback) => void): (a: A1) =>
7 | Promise;
8 | function promisify(
9 | f: (a: A1, a2: A2, cb: NodeCallback) => void): (a: A1, a2: A2) =>
10 | Promise;
11 | function promisify(
12 | f: (a: A1, a2: A2, a3: A3, cb: NodeCallback) =>
13 | void): (a: A1, a2: A2, a3: A3) => Promise;
14 | namespace promisify {}
15 | export = promisify;
16 | }
17 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/projection-element.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | four
11 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/browser/more-declarations.ts:
--------------------------------------------------------------------------------
1 | declare namespace Mocha {
2 | interface UtilsStatic {
3 | highlightTags(somethingSomething: string): void;
4 | }
5 | let utils: UtilsStatic;
6 | interface IRunner extends NodeJS.EventEmitter {
7 | name?: string;
8 | total: number;
9 | }
10 |
11 | interface IRunnable {
12 | parent: ISuite;
13 | root: boolean;
14 | state: 'passed'|'failed'|undefined;
15 | pending: boolean;
16 | }
17 |
18 | interface ISuite {
19 | root: boolean;
20 | }
21 |
22 | let Runner: {prototype: IRunner; immediately(callback: () => void): void};
23 | }
24 |
25 | declare namespace SocketIO {
26 | interface Server {
27 | off(): void;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 | addons:
4 | firefox: latest
5 | apt:
6 | sources:
7 | - google-chrome
8 | packages:
9 | - google-chrome-stable
10 | sauce_connect: true
11 | language: node_js
12 | node_js:
13 | - node
14 | - '6'
15 | script:
16 | - xvfb-run npm test
17 | env:
18 | global:
19 | - secure: erPJ5uPudGD1E05tCYCWk/sdSFYL5lK/7rzUPgijC27XtuTF0tvvLKJkId2sCzPb40yCBHhEv0UIVEnYzY4feCpx5kUqm+Iu6DHWDn4xJP+gRnCNMCJp0QcUFU3JDNGK07FG2mKUkxD8EHxYjATL7tnN9HduNxaornVapkgRu70=
20 | - secure: WkrrR4HpJr1jD86mOegdumhLqI5M1skh2j1iX0AYF41oPym4CIRHvFA8REY1f12OlPXfXCVZL8+7nyuoM6NEyddEDzZZXmgAJVFkFbP++veVjXGVAWsr2++n5lzcUfvhIuYTxTqSTpnA6E3DhoJdXBxt0FBTFxYA3yXSIfJsu2k=
21 |
--------------------------------------------------------------------------------
/test/fixtures/integration/query-string/test/tests.js:
--------------------------------------------------------------------------------
1 | // TODO(usergenic): Figure out a reasonable solution to get URL() and
2 | // document.currentScript to work in IE11 and then put these tests back
3 | // in commission.
4 | //
5 | // See https://github.com/PolymerElements/iron-location/blob/3ef6d758514d7cb80a3297f8ef5208774d486e88/iron-location.html#L65
6 | // as a possible solution.
7 | /*
8 | var url = new URL(document.currentScript.src);
9 |
10 | test('preserves query strings (' + url.search + ')', function () {
11 | expect(url.search).to.match(/\?fizz=buzz/);
12 | });
13 | */
14 | test('preserves query strings (?fizz=buzz&foo=bar)', function () { });
15 | test('preserves query strings (?fizz=buzz)', function () { });
16 |
--------------------------------------------------------------------------------
/browser/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "es6",
5 | "moduleResolution": "node",
6 | "isolatedModules": false,
7 | "noImplicitAny": true,
8 | "noUnusedLocals": false,
9 | "noUnusedParameters": true,
10 | "noImplicitThis": false,
11 | "strictNullChecks": false,
12 | "removeComments": false,
13 | "preserveConstEnums": true,
14 | "suppressImplicitAnyIndexErrors": true,
15 | "lib": [
16 | "es2017",
17 | "dom"
18 | ],
19 | "sourceMap": true,
20 | "pretty": true
21 | },
22 | "exclude": [
23 | "node_modules"
24 | ],
25 | "include": [
26 | "*.ts",
27 | "**/*.ts",
28 | "../custom_typings/*.d.ts"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/browser-js-header.txt:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9 | */
10 |
11 | /**
12 | * THIS FILE IS AUTOMATICALLY GENERATED!
13 | * To make changes to browser.js, please edit the source files in the repo's `browser/` directory!
14 | */
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "clang-format.style": "file",
4 | "editor.formatOnSave": true,
5 | "editor.formatOnType": true,
6 | "files.exclude": {
7 | "runner/*.js": true,
8 | "runner/*.d.ts": true,
9 | "runner/*.js.map": true,
10 | "test/unit/*.js": true,
11 | "test/unit/*.d.ts": true,
12 | "test/unit/*.js.map": true,
13 | "test/integration/*.js": true,
14 | "test/integration/*.d.ts": true,
15 | "test/integration/*.js.map": true,
16 | "browser.js": true,
17 | "browser.js.map": true,
18 | "browser/**/*.js": true,
19 | "browser/**/*.js.map": true
20 | },
21 | "typescript.tsdk": "./node_modules/typescript/lib"
22 | }
23 |
--------------------------------------------------------------------------------
/custom_typings/send.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'send' {
2 | import * as http from 'http';
3 | import * as events from 'events';
4 |
5 |
6 | function send(req: http.IncomingMessage, path: string, options?: send.Options): send.SendStream;
7 | namespace send {
8 | export interface SendStream extends events.EventEmitter {
9 | pipe(res: http.ServerResponse): void;
10 | }
11 |
12 | export interface Options {
13 | dotfiles?: 'allow' | 'deny' | 'ignore';
14 | end?: number;
15 | etag?: boolean;
16 | extensions?: string[];
17 | index?: boolean|string|string[];
18 | lastModified?: boolean;
19 | maxAge?: number;
20 | root?: string;
21 | start?: number;
22 | }
23 | }
24 | export = send;
25 | }
26 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/dom-if-element.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 |
28 |
--------------------------------------------------------------------------------
/runner.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9 | */
10 | module.exports = {
11 | cli: require('./runner/cli'),
12 | config: require('./runner/config'),
13 | gulp: require('./runner/gulp'),
14 | steps: require('./runner/steps'),
15 | test: require('./runner/test'),
16 | };
17 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 10,
3 | "pending": 0,
4 | "failing": 0,
5 | "status": "complete",
6 | "tests": {
7 | "test/tests.html": {
8 | "testing projected element replacement": {
9 | "projection replace test": {"state": "passing"}
10 | },
11 | "testing standard multiple replace": {
12 | "double replace test": {"state": "passing"}
13 | },
14 | "testing template bindings": {
15 | "dom-repeat bindings exist": {"state": "passing"}
16 | },
17 | "testing template reset after test": {
18 | "checking template is reset after replace test": {"state": "passing"}
19 | },
20 | "testing templatized multiple replace": {
21 | "dom-if test": {"state": "passing"}
22 | }
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "isolatedModules": false,
7 | "noImplicitAny": true,
8 | "noUnusedLocals": false,
9 | "noUnusedParameters": true,
10 | "noImplicitThis": false,
11 | "strictNullChecks": false,
12 | "removeComments": false,
13 | "preserveConstEnums": true,
14 | "suppressImplicitAnyIndexErrors": true,
15 | "lib": ["es2017"],
16 | "declaration": true,
17 | "sourceMap": true,
18 | "pretty": true
19 | },
20 | "exclude": [
21 | "node_modules"
22 | ],
23 | "include": [
24 | "runner/*.ts",
25 | "test/**/*.ts",
26 | "custom_typings/*.d.ts",
27 | "node_modules/@types/mocha/index.d.ts"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/browser/mocha/fixture.ts:
--------------------------------------------------------------------------------
1 | import {extendInterfaces} from './extend.js';
2 |
3 | interface TestFixture extends HTMLElement {
4 | create(model: object): HTMLElement;
5 | restore(): void;
6 | }
7 |
8 | extendInterfaces('fixture', function(context, teardown) {
9 |
10 | // Return context.fixture if it is already a thing, for backwards
11 | // compatibility with `test-fixture-mocha.js`:
12 | return context.fixture || function fixture(fixtureId: string, model: object) {
13 |
14 | // Automatically register a teardown callback that will restore the
15 | // test-fixture:
16 | teardown(function() {
17 | (document.getElementById(fixtureId) as TestFixture).restore();
18 | });
19 |
20 | // Find the test-fixture with the provided ID and create it, returning
21 | // the results:
22 | return (document.getElementById(fixtureId) as TestFixture).create(model);
23 | };
24 | });
25 |
--------------------------------------------------------------------------------
/custom_typings/stacky.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'stacky' {
2 | interface ParsedStackFrame {
3 | method: string;
4 | location: string;
5 | line: number;
6 | column: number;
7 | }
8 | type StyleFunction = (part: string) => string;
9 | interface Options {
10 | maxMethodPadding?: number;
11 | indent?: string;
12 | methodPlaceholder?: string;
13 | locationStrip?: (string|RegExp)[];
14 | unimportantLocation?: (string|RegExp)[];
15 | filter?: (line: ParsedStackFrame) => boolean;
16 | styles?: {
17 | method?: StyleFunction;
18 | location?: StyleFunction;
19 | line?: StyleFunction;
20 | column?: StyleFunction;
21 | unimportant?: StyleFunction;
22 | };
23 | }
24 | export function clean(lines: ParsedStackFrame[], options: Options): void;
25 | export function pretty(
26 | errorStack: string|ParsedStackFrame[], options: Options): string;
27 |
28 | export function normalize(error: Error, config: Options): Error;
29 | }
30 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/dom-repeat-fixture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{boundValue}}
8 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
41 |
42 |
--------------------------------------------------------------------------------
/tasks/test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9 | */
10 | var chalk = require('chalk');
11 |
12 | var test = require('../runner/test');
13 |
14 | module.exports = function(grunt) {
15 | grunt.registerMultiTask('wct-test', 'Runs tests via web-component-tester', function() {
16 | var done = this.async();
17 | test(this.options()).then(() => done(), (error) => {
18 | console.log(chalk.red(error));
19 | // Grunt only errors on `false` and instances of `Error`.
20 | done(new Error(error));
21 | });
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/bin/wct:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * @license
4 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
5 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
7 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
8 | * Code distributed by Google as part of the polymer project is also
9 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | var resolve = require('resolve');
12 |
13 | process.title = 'wct';
14 |
15 | resolve('web-component-tester', {basedir: process.cwd()}, function(error, path) {
16 | var wct = path ? require(path) : require('..');
17 | var promise = wct.cli.run(process.env, process.argv.slice(2), process.stdout, function (error) {
18 | process.exit(error ? 1 : 0);
19 | });
20 | if (promise) {
21 | promise.then(() => process.exit(0), () => process.exit(1));
22 | }
23 | });
24 |
--------------------------------------------------------------------------------
/bin/wct-st:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | * @license
4 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
5 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
7 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
8 | * Code distributed by Google as part of the polymer project is also
9 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | var resolve = require('resolve');
12 |
13 | process.title = 'wct-st';
14 |
15 | resolve('web-component-tester', {basedir: process.cwd()}, function(error, path) {
16 | var wct = path ? require(path) : require('..');
17 | var promise = wct.cli.runSauceTunnel(process.env, process.argv.slice(2), process.stdout, function (error) {
18 | process.exit(error ? 1 : 0);
19 | });
20 | if (promise) {
21 | promise.then(() => process.exit(0), () => process.exit(1));
22 | }
23 | });
24 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-component-tester",
3 | "description": "web-component-tester makes testing your web components a breeze!",
4 | "version": "6.0.0",
5 | "main": [
6 | "browser.js"
7 | ],
8 | "license": "http://polymer.github.io/LICENSE.txt",
9 | "ignore": [
10 | "*",
11 | "!/data/*",
12 | "!/browser.js",
13 | "!/browser.js.map",
14 | "!/package.json",
15 | "!/bower.json"
16 | ],
17 | "keywords": [
18 | "browser",
19 | "grunt",
20 | "gruntplugin",
21 | "gulp",
22 | "polymer",
23 | "test",
24 | "testing",
25 | "web component",
26 | "web"
27 | ],
28 | "dependencies": {
29 | "accessibility-developer-tools": "^2.10.0",
30 | "async": "^1.5.0",
31 | "chai": "^3.2.0",
32 | "lodash": "^3.7.0",
33 | "mocha": "^3.1.2",
34 | "sinon-chai": "^2.7.0",
35 | "sinonjs": "^1.14.1",
36 | "stacky": "^1.3.0",
37 | "test-fixture": "^3.0.0"
38 | },
39 | "devDependencies": {
40 | "polymer": "Polymer/polymer#^1.5.0",
41 | "webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.22"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/fixtures/integration/failing/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "passing": 3,
3 | "pending": 0,
4 | "failing": 3,
5 | "status": "complete",
6 | "tests": {
7 | "test/": {
8 | "failing test": {
9 | "state": "failing"
10 | },
11 | "inline failing test": {
12 | "state": "failing"
13 | },
14 | "inline passing test": {
15 | "state": "passing"
16 | },
17 | "passing test": {
18 | "state": "passing"
19 | }
20 | },
21 | "test/tests.html": {
22 | "failing test": {
23 | "state": "failing"
24 | },
25 | "passing test": {
26 | "state": "passing"
27 | }
28 | }
29 | },
30 | "errors": {
31 | "test/": {
32 | "inline failing test": [
33 | "expected false to be true",
34 | "at index\\.html:(12|15)(:|$)"
35 | ],
36 | "failing test": [
37 | "expected false to be true",
38 | "at tests\\.js:3(:|$)"
39 | ]
40 | },
41 | "test/tests.html": {
42 | "failing test": [
43 | "expected false to be true",
44 | "at tests\\.html:(10|13)(:|$)"
45 | ]
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/wct-browser-legacy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wct-browser-legacy",
3 | "version": "0.0.1-pre.11",
4 | "description": "Client-side dependencies for web-component-tester tests installed via npm.",
5 | "main": "browser.js",
6 | "license": "http://polymer.github.io/LICENSE.txt",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/polymer/web-component-tester.git"
13 | },
14 | "keywords": [
15 | "browser",
16 | "gulp",
17 | "polymer",
18 | "test",
19 | "testing",
20 | "web",
21 | "web component"
22 | ],
23 | "bugs": {
24 | "url": "https://github.com/polymer/web-component-tester/issues"
25 | },
26 | "homepage": "https://github.com/polymer/web-component-tester#readme",
27 | "dependencies": {
28 | "@polymer/polymer": "^3.0.0-pre.1",
29 | "@polymer/sinonjs": "^1.14.1",
30 | "@polymer/test-fixture": "^3.0.0-pre.1",
31 | "@webcomponents/webcomponentsjs": "^1.0.7",
32 | "accessibility-developer-tools": "^2.12.0",
33 | "async": "^1.5.2",
34 | "chai": "^3.5.0",
35 | "lodash": "^3.10.1",
36 | "mocha": "^3.4.2",
37 | "sinon": "^1.17.1",
38 | "sinon-chai": "^2.10.0",
39 | "stacky": "^1.3.1"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/browser/environment/errors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import * as config from '../config.js';
12 |
13 | // We may encounter errors during initialization (for example, syntax errors in
14 | // a test file). Hang onto those (and more) until we are ready to report them.
15 | export let globalErrors: any[] = [];
16 |
17 | /**
18 | * Hook the environment to pick up on global errors.
19 | */
20 | export function listenForErrors() {
21 | window.addEventListener('error', function(event) {
22 | globalErrors.push(event.error);
23 | });
24 |
25 | // Also, we treat `console.error` as a test failure. Unless you prefer not.
26 | const origConsole = console;
27 | const origError = console.error;
28 | console.error = function wctShimmedError() {
29 | origError.apply(origConsole, arguments);
30 | if (config.get('trackConsoleError')) {
31 | throw 'console.error: ' + Array.prototype.join.call(arguments, ' ');
32 | }
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-component_dirs/golden.json:
--------------------------------------------------------------------------------
1 | {
2 | "variants": {
3 | "": {
4 | "passing": 1,
5 | "pending": 0,
6 | "failing": 0,
7 | "status": "complete",
8 | "tests": {
9 | "test/": {
10 | "only works with mainline components": {
11 | "state": "passing"
12 | }
13 | }
14 | }
15 | },
16 | "foo": {
17 | "passing": 0,
18 | "pending": 0,
19 | "failing": 1,
20 | "status": "complete",
21 | "tests": {
22 | "test/": {
23 | "only works with mainline components": {
24 | "state": "failing"
25 | }
26 | }
27 | },
28 | "errors": {
29 | "test/": {
30 | "only works with mainline components": [
31 | "expected 'foo' to equal 'mainline'",
32 | "at index\\.html:(10|13)"
33 | ]
34 | }
35 | }
36 | },
37 | "bar": {
38 | "passing": 0,
39 | "pending": 0,
40 | "failing": 1,
41 | "status": "complete",
42 | "tests": {
43 | "test/": {
44 | "only works with mainline components": {
45 | "state": "failing"
46 | }
47 | }
48 | },
49 | "errors": {
50 | "test/": {
51 | "only works with mainline components": [
52 | "expected 'bar' to equal 'mainline'",
53 | "at index\\.html:(10|13)"
54 | ]
55 | }
56 | }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/browser/mocha/stub.ts:
--------------------------------------------------------------------------------
1 | import {extendInterfaces} from './extend.js';
2 |
3 | /**
4 | * stub
5 | *
6 | * The stub addon allows the tester to partially replace the implementation of
7 | * an element with some custom implementation. Usage example:
8 | *
9 | * beforeEach(function() {
10 | * stub('x-foo', {
11 | * attached: function() {
12 | * // Custom implementation of the `attached` method of element `x-foo`..
13 | * },
14 | * otherMethod: function() {
15 | * // More custom implementation..
16 | * },
17 | * getterSetterProperty: {
18 | * get: function() {
19 | * // Custom getter implementation..
20 | * },
21 | * set: function() {
22 | * // Custom setter implementation..
23 | * }
24 | * },
25 | * // etc..
26 | * });
27 | * });
28 | */
29 | extendInterfaces('stub', function(_context, teardown) {
30 |
31 | return function stub(tagName: string, implementation: object) {
32 | // Find the prototype of the element being stubbed:
33 | const proto = document.createElement(tagName).constructor.prototype;
34 |
35 | // For all keys in the implementation to stub with..
36 | const stubs = Object.keys(implementation).map(function(key) {
37 | // Stub the method on the element prototype with Sinon:
38 | return sinon.stub(proto, key, implementation[key]);
39 | });
40 |
41 | // After all tests..
42 | teardown(function() {
43 | stubs.forEach(function(stub) {
44 | stub.restore();
45 | });
46 | });
47 | };
48 | });
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 The Polymer Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/runner/gulp.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | import * as chalk from 'chalk';
16 | import {Gulp} from 'gulp';
17 |
18 | import {Config} from './config';
19 | import {test} from './test';
20 |
21 |
22 | export function init(gulp: Gulp, dependencies?: string[]): void {
23 | if (!dependencies) {
24 | dependencies = [];
25 | }
26 |
27 | // TODO(nevir): Migrate fully to wct:local/etc.
28 | gulp.task('test', ['wct:local']);
29 | gulp.task('test:local', ['wct:local']);
30 | gulp.task('test:remote', ['wct:sauce']);
31 |
32 | gulp.task('wct', ['wct:local']);
33 |
34 | gulp.task('wct:local', dependencies, () => {
35 | return test({plugins: {local: {}, sauce: false}}).catch(cleanError);
36 | });
37 |
38 | gulp.task('wct:sauce', dependencies, () => {
39 | return test({plugins: {local: false, sauce: {}}}).catch(cleanError);
40 | });
41 | }
42 |
43 | // Utility
44 |
45 | function cleanError(error: any) {
46 | // Pretty error for gulp.
47 | error = new Error(chalk.red(error.message || error));
48 | error.showStack = false;
49 | throw error;
50 | }
51 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "arrow-parens": true,
4 | "class-name": true,
5 | "indent": [
6 | true,
7 | "spaces"
8 | ],
9 | "prefer-const": true,
10 | "no-duplicate-variable": true,
11 | "no-eval": true,
12 | "no-internal-module": true,
13 | "no-trailing-whitespace": true,
14 | "no-var-keyword": true,
15 | "one-line": [
16 | true,
17 | "check-open-brace",
18 | "check-whitespace"
19 | ],
20 | "quotemark": [
21 | true,
22 | "single",
23 | "avoid-escape"
24 | ],
25 | "semicolon": [
26 | true,
27 | "always"
28 | ],
29 | "trailing-comma": [
30 | true,
31 | "multiline"
32 | ],
33 | "triple-equals": [
34 | true,
35 | "allow-null-check"
36 | ],
37 | "typedef-whitespace": [
38 | true,
39 | {
40 | "call-signature": "nospace",
41 | "index-signature": "nospace",
42 | "parameter": "nospace",
43 | "property-declaration": "nospace",
44 | "variable-declaration": "nospace"
45 | }
46 | ],
47 | "variable-name": [
48 | true,
49 | "ban-keywords"
50 | ],
51 | "whitespace": [
52 | true,
53 | "check-branch",
54 | "check-decl",
55 | "check-operator",
56 | "check-separator",
57 | "check-type"
58 | ]
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/browser/environment/compatability.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import ChildRunner from '../childrunner.js';
12 |
13 | // polymer-test-tools (and Polymer/tools) support HTML tests where each file is
14 | // expected to call `done()`, which posts a message to the parent window.
15 | window.addEventListener('message', function(event) {
16 | if (!event.data || (event.data !== 'ok' && !event.data.error)) {
17 | return;
18 | }
19 | const childRunner = ChildRunner.get(event.source);
20 | if (!childRunner) {
21 | return;
22 | }
23 |
24 | childRunner.ready();
25 | // The name of the suite as exposed to the user.
26 | const reporter = childRunner.parentScope.WCT._reporter;
27 | const title = reporter.suiteTitle(event.source.location);
28 | reporter.emitOutOfBandTest(
29 | 'page-wide tests via global done()', event.data.error, title, true);
30 |
31 | childRunner.done();
32 | });
33 |
34 | // Attempt to ensure that we complete a test suite if it is interrupted by a
35 | // document unload.
36 | window.addEventListener('unload', function(_event: BeforeUnloadEvent) {
37 | // Mocha's hook queue is asynchronous; but we want synchronous behavior if
38 | // we've gotten to the point of unloading the document.
39 | Mocha.Runner.immediately = function(callback: () => void) {
40 | callback();
41 | };
42 | });
43 |
--------------------------------------------------------------------------------
/browser/declarations.ts:
--------------------------------------------------------------------------------
1 | import * as ChaiStatic from 'chai';
2 | import * as SinonStatic from 'sinon';
3 | import * as SocketIOStatic from 'socket.io';
4 | import * as StackyStatic from 'stacky';
5 |
6 | import {default as ChildRunner, SharedState} from './childrunner.js';
7 | import {Config} from './config.js';
8 | import MultiReporter from './reporters/multi.js';
9 | import * as suites from './suites.js';
10 |
11 | type loadSuitesType = (typeof suites.loadSuites);
12 |
13 | declare global {
14 | interface Window {
15 | __wctUseNpm?: boolean;
16 | WebComponents?: WebComponentsStatic;
17 | Platform?: PlatformStatic;
18 | Polymer?: PolymerStatic;
19 | WCT: {
20 | readonly _ChildRunner: typeof ChildRunner; //
21 | readonly share: SharedState; //
22 | readonly _config: Config; //
23 | readonly loadSuites: loadSuitesType;
24 | _reporter: MultiReporter;
25 | };
26 | mocha: typeof mocha;
27 | Mocha: typeof Mocha;
28 | __generatedByWct?: boolean;
29 |
30 | chai: typeof ChaiStatic;
31 | assert: typeof ChaiStatic.assert;
32 | expect: typeof ChaiStatic.expect;
33 | }
34 | interface WebComponentsStatic {
35 | ready?(): void;
36 | flush?(): void;
37 | }
38 | interface PlatformStatic {
39 | performMicrotaskCheckpoint(): void;
40 | }
41 | interface PolymerElement {
42 | _stampTemplate?(): void;
43 | }
44 | interface PolymerElementConstructor {
45 | prototype: PolymerElement;
46 | }
47 | interface PolymerStatic {
48 | flush(): void;
49 | dom: {flush(): void};
50 | Element: PolymerElementConstructor;
51 | }
52 |
53 | interface Element {
54 | isConnected?: boolean;
55 | }
56 |
57 | interface Mocha {
58 | suite: Mocha.ISuite;
59 | }
60 |
61 | var Stacky: typeof StackyStatic;
62 | var io: typeof SocketIOStatic;
63 | var Platform: PlatformStatic;
64 | var sinon: typeof SinonStatic;
65 | }
66 |
--------------------------------------------------------------------------------
/custom_typings/wd.d.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | declare module 'wd' {
4 | interface NodeCB {
5 | (err: any, value: T): void;
6 | }
7 | export interface Browser {
8 | configureHttp(options: {
9 | retries: number
10 | }): void;
11 | attach(sessionId: string, callback: NodeCB): void;
12 | init(capabilities: Capabilities, callback: NodeCB): void;
13 |
14 | get(url: string, callback: NodeCB): void;
15 | quit(callback: NodeCB): void;
16 |
17 | on(eventName: string, handler: Function): void;
18 | }
19 | export interface Capabilities {
20 | /** The name of the browser being used */
21 | browserName: 'android'|'chrome'|'firefox'|'htmlunit'|'internet explorer'|'iPhone'|'iPad'|'opera'|'safari';
22 | /** The browser version, or the empty string if unknown. */
23 | version: string;
24 | /** A key specifying which platform the browser should be running on. */
25 | platform: 'WINDOWS'|'XP'|'VISTA'|'MAC'|'LINUX'|'UNIX'|'ANDROID'|'ANY';
26 |
27 | /** Whether the session can interact with modal popups,
28 | * such as window.alert and window.confirm. */
29 | handlesAlerts: boolean;
30 | /** Whether the session supports CSS selectors when searching for elements. */
31 | cssSelectorsEnabled: boolean;
32 |
33 | webdriver: {
34 | remote: {
35 | quietExceptions: boolean;
36 | }
37 | };
38 |
39 | selenium: {
40 | server: {
41 | url: string;
42 | }
43 | };
44 | }
45 |
46 | export function remote(
47 | hostnameOrUrl: string, port?: number,
48 | username?: string, password?: string): Browser;
49 | export function remote(
50 | options: {hostname: string, port?: number,
51 | auth?: string, path?: string, }
52 | ): Browser;
53 | export function remote(
54 | options: {host: string, port?: number,
55 | username?: string, accesskey?: string, path?: string, }
56 | ): Browser;
57 | }
58 |
--------------------------------------------------------------------------------
/runner/port-scanner.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | import * as net from 'net';
16 |
17 | function checkPort(port: number): Promise {
18 | return new Promise(function(resolve) {
19 | const server = net.createServer();
20 | let hasPort = false;
21 |
22 | // if server is listening, we have the port!
23 | server.on('listening', function(_err: any) {
24 | hasPort = true;
25 | server.close();
26 | });
27 |
28 | // callback on server close to free up the port before report it can be used
29 | server.on('close', function(_err: any) {
30 | resolve(hasPort);
31 | });
32 |
33 | // our port is busy, ignore it
34 | server.on('error', function(_err: any) {
35 | // docs say the server should close, this doesn't seem to be the case :(
36 | server.close();
37 | });
38 |
39 | server.listen(port);
40 | });
41 | }
42 |
43 | interface PromiseGetter {
44 | (val: T): Promise;
45 | }
46 |
47 | async function detectSeries(
48 | values: T[], promiseGetter: PromiseGetter): Promise {
49 | for (const value of values) {
50 | if (await promiseGetter(value)) {
51 | return value;
52 | }
53 | }
54 | throw new Error('Couldn\'t find a good value in detectSeries');
55 | }
56 |
57 | export async function findPort(ports: number[]): Promise {
58 | try {
59 | return await detectSeries(ports, checkPort);
60 | } catch (error) {
61 | throw new Error('no port found!');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/browser/reporters/html.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 |
12 | /**
13 | * WCT-specific behavior on top of Mocha's default HTML reporter.
14 | *
15 | * @param {!Mocha.Runner} runner The runner that is being reported on.
16 | */
17 | export default function HTML(runner: Mocha.IRunner) {
18 | const output = document.createElement('div');
19 | output.id = 'mocha';
20 | document.body.appendChild(output);
21 |
22 | runner.on('suite', function(_test: any) {
23 | this.total = runner.total;
24 | }.bind(this));
25 |
26 | Mocha.reporters.HTML.call(this, runner);
27 | }
28 |
29 | // Woo! What a hack. This just saves us from adding a bunch of complexity around
30 | // style loading.
31 | const style = document.createElement('style');
32 | style.textContent = `
33 | html, body {
34 | position: relative;
35 | height: 100%;
36 | width: 100%;
37 | min-width: 900px;
38 | }
39 | #mocha, #subsuites {
40 | height: 100%;
41 | position: absolute;
42 | top: 0;
43 | }
44 | #mocha {
45 | box-sizing: border-box;
46 | margin: 0 !important;
47 | padding: 60px 20px;
48 | right: 0;
49 | left: 500px;
50 | }
51 | #subsuites {
52 | -ms-flex-direction: column;
53 | -webkit-flex-direction: column;
54 | display: -ms-flexbox;
55 | display: -webkit-flex;
56 | display: flex;
57 | flex-direction: column;
58 | left: 0;
59 | width: 500px;
60 | }
61 | #subsuites .subsuite {
62 | border: 0;
63 | width: 100%;
64 | height: 100%;
65 | }
66 | #mocha .test.pass .duration {
67 | color: #555 !important;
68 | }
69 | `;
70 | document.head.appendChild(style);
71 |
--------------------------------------------------------------------------------
/browser/reporters.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import CLISocket from './clisocket.js';
12 | import ConsoleReporter from './reporters/console.js';
13 | import HTMLReporter from './reporters/html.js';
14 | import MultiReporter, {ReporterFactory} from './reporters/multi.js';
15 | import TitleReporter from './reporters/title.js';
16 | import * as suites from './suites.js';
17 |
18 | export let htmlSuites: Array = [];
19 | export let jsSuites: Array = [];
20 |
21 | /**
22 | * @param {CLISocket} socket The CLI socket, if present.
23 | * @param {MultiReporter} parent The parent reporter, if present.
24 | * @return {!Array. = [TitleReporter, ConsoleReporter];
35 |
36 | if (socket) {
37 | reporters.push(function(runner: MultiReporter) {
38 | socket.observe(runner);
39 | } as any);
40 | }
41 |
42 | if (suites.htmlSuites.length > 0 || suites.jsSuites.length > 0) {
43 | reporters.push(HTMLReporter as any);
44 | }
45 |
46 | return reporters;
47 | }
48 |
49 | export type MochaStatic = typeof Mocha;
50 | /**
51 | * Yeah, hideous, but this allows us to be loaded before Mocha, which is handy.
52 | */
53 | export function injectMocha(Mocha: MochaStatic) {
54 | _injectPrototype(ConsoleReporter, Mocha.reporters.Base.prototype);
55 | _injectPrototype(HTMLReporter, Mocha.reporters.HTML.prototype);
56 | // Mocha doesn't expose its `EventEmitter` shim directly, so:
57 | _injectPrototype(
58 | MultiReporter, Object.getPrototypeOf(Mocha.Runner.prototype));
59 | }
60 |
61 | function _injectPrototype(klass: any, prototype: any) {
62 | const newPrototype = Object.create(prototype);
63 | // Only support
64 | Object.keys(klass.prototype).forEach(function(key) {
65 | newPrototype[key] = klass.prototype[key];
66 | });
67 |
68 | klass.prototype = newPrototype;
69 | }
70 |
--------------------------------------------------------------------------------
/browser/mocha/extend.ts:
--------------------------------------------------------------------------------
1 |
2 | const interfaceExtensions: Array<() => void> = [];
3 |
4 | /**
5 | * Registers an extension that extends the global `Mocha` implementation
6 | * with new helper methods. These helper methods will be added to the `window`
7 | * when tests run for both BDD and TDD interfaces.
8 | */
9 | export function extendInterfaces(
10 | helperName: string,
11 | helperFactory: (
12 | context: any, teardown: (cb: () => void) => void,
13 | interfaceName: 'tdd'|'bdd') => void) {
14 | interfaceExtensions.push(function() {
15 | const Mocha = window.Mocha;
16 | // For all Mocha interfaces (probably just TDD and BDD):
17 | Object.keys((Mocha as any).interfaces)
18 | .forEach(function(interfaceName: 'tdd'|'bdd') {
19 | // This is the original callback that defines the interface (TDD or
20 | // BDD):
21 | const originalInterface = (Mocha as any).interfaces[interfaceName];
22 | // This is the name of the "teardown" or "afterEach" property for the
23 | // current interface:
24 | const teardownProperty =
25 | interfaceName === 'tdd' ? 'teardown' : 'afterEach';
26 | // The original callback is monkey patched with a new one that appends
27 | // to the global context however we want it to:
28 | (Mocha as any).interfaces[interfaceName] = function(suite: any) {
29 | // Call back to the original callback so that we get the base
30 | // interface:
31 | originalInterface.apply(this, arguments);
32 | // Register a listener so that we can further extend the base
33 | // interface:
34 | suite.on(
35 | 'pre-require',
36 | function(context: any, _file: string, _mocha: any) {
37 | // Capture a bound reference to the teardown function as a
38 | // convenience:
39 | const teardown = context[teardownProperty].bind(context);
40 | // Add our new helper to the testing context. The helper is
41 | // generated by a factory method that receives the context,
42 | // the teardown function and the interface name and returns
43 | // the new method to be added to that context:
44 | context[helperName] =
45 | helperFactory(context, teardown, interfaceName);
46 | });
47 | };
48 | });
49 | });
50 | }
51 |
52 | /**
53 | * Applies any registered interface extensions. The extensions will be applied
54 | * as many times as this function is called, so don't call it more than once.
55 | */
56 | export function applyExtensions() {
57 | interfaceExtensions.forEach(function(applyExtension) {
58 | applyExtension();
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/runner/test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import * as cleankill from 'cleankill';
15 |
16 | import {CliReporter} from './clireporter';
17 | import {Config} from './config';
18 | import {Context} from './context';
19 | import * as steps from './steps';
20 |
21 | /**
22 | * Runs a suite of web component tests.
23 | *
24 | * The returned Context (a kind of EventEmitter) fires various events to allow
25 | * you to track the progress of the tests:
26 | *
27 | * Lifecycle Events:
28 | *
29 | * `run-start`
30 | * WCT is ready to begin spinning up browsers.
31 | *
32 | * `browser-init` {browser} {stats}
33 | * WCT is ready to begin spinning up browsers.
34 | *
35 | * `browser-start` {browser} {metadata} {stats}
36 | * The browser has begun running tests. May fire multiple times (i.e. when
37 | * manually refreshing the tests).
38 | *
39 | * `sub-suite-start` {browser} {sharedState} {stats}
40 | * A suite file has begun running.
41 | *
42 | * `test-start` {browser} {test} {stats}
43 | * A test has begun.
44 | *
45 | * `test-end` {browser} {test} {stats}
46 | * A test has ended.
47 | *
48 | * `sub-suite-end` {browser} {sharedState} {stats}
49 | * A suite file has finished running all of its tests.
50 | *
51 | * `browser-end` {browser} {error} {stats}
52 | * The browser has completed, and it shutting down.
53 | *
54 | * `run-end` {error}
55 | * WCT has run all browsers, and is shutting down.
56 | *
57 | * Generic Events:
58 | *
59 | * * log:debug
60 | * * log:info
61 | * * log:warn
62 | * * log:error
63 | *
64 | * @param {!Config|!Context} options The configuration or an already formed
65 | * `Context` object.
66 | */
67 | export async function test(options: Config|Context): Promise {
68 | const context = (options instanceof Context) ? options : new Context(options);
69 |
70 | // We assume that any options related to logging are passed in via the initial
71 | // `options`.
72 | if (context.options.output) {
73 | new CliReporter(context, context.options.output, context.options);
74 | }
75 |
76 | try {
77 | await steps.setupOverrides(context);
78 | await steps.loadPlugins(context);
79 | await steps.configure(context);
80 | await steps.prepare(context);
81 | await steps.runTests(context);
82 | } finally {
83 | if (!context.options.skipCleanup) {
84 | await cleankill.close();
85 | }
86 | }
87 | }
88 |
89 | // HACK
90 | test['test'] = test;
91 |
92 | module.exports = test;
93 |
--------------------------------------------------------------------------------
/runner/paths.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | import * as fs from 'fs';
16 | import * as glob from 'glob';
17 | import * as _ from 'lodash';
18 | import * as path from 'path';
19 | import * as promisify from 'promisify-node';
20 |
21 | /**
22 | * Expands a series of path patterns (globs, files, directories) into a set of
23 | * files that represent those patterns.
24 | *
25 | * @param baseDir The directory that patterns are relative to.
26 | * @param patterns The patterns to expand.
27 | * @returns The expanded paths.
28 | */
29 | export async function expand(
30 | baseDir: string, patterns: string[]): Promise {
31 | return expandDirectories(baseDir, await unglob(baseDir, patterns));
32 | }
33 |
34 | /**
35 | * Expands any glob expressions in `patterns`.
36 | */
37 | async function unglob(baseDir: string, patterns: string[]): Promise {
38 | const strs: string[][] = [];
39 | const pGlob: any = promisify(glob);
40 | for (const pattern of patterns) {
41 | strs.push(await pGlob(String(pattern), {cwd: baseDir, root: baseDir}));
42 | }
43 |
44 | // for non-POSIX support, replacing path separators
45 | return _.union(_.flatten(strs)).map((str) => str.replace(/\//g, path.sep));
46 | }
47 |
48 | /**
49 | * Expands any directories in `patterns`, following logic similar to a web
50 | * server.
51 | *
52 | * If a pattern resolves to a directory, that directory is expanded. If the
53 | * directory contains an `index.html`, it is expanded to that. Otheriwse, the
54 | * it expands into its children (recursively).
55 | */
56 | async function expandDirectories(baseDir: string, paths: string[]) {
57 | const listsOfPaths: string[][] = [];
58 | for (const aPath of paths) {
59 | listsOfPaths.push(await expandDirectory(baseDir, aPath));
60 | }
61 |
62 | const files = _.union(_.flatten(listsOfPaths));
63 | return files.filter((file) => /\.(js|html)$/.test(file));
64 | }
65 |
66 | async function expandDirectory(
67 | baseDir: string, aPath: string): Promise {
68 | const stat = await promisify(fs.stat)(path.resolve(baseDir, aPath));
69 | if (!stat.isDirectory()) {
70 | return [aPath];
71 | }
72 | const files = await promisify(fs.readdir)(path.resolve(baseDir, aPath));
73 | // We have an index; defer to that.
74 | if (_.includes(files, 'index.html')) {
75 | return [path.join(aPath, 'index.html')];
76 | }
77 | const children = await expandDirectories(path.join(baseDir, aPath), files);
78 | return children.map((child) => path.join(aPath, child));
79 | }
80 |
--------------------------------------------------------------------------------
/test/unit/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import * as chai from 'chai';
15 |
16 | import * as config from '../../runner/config';
17 | import {Context} from '../../runner/context';
18 | const expect = chai.expect;
19 |
20 | describe('config', function() {
21 |
22 | describe('.merge', function() {
23 |
24 | it('avoids modifying the input', function() {
25 | const one = {foo: 1};
26 | const two = {foo: 2};
27 | const merged = config.merge(one, two);
28 |
29 | expect(one.foo).to.eq(1);
30 | expect(two.foo).to.eq(2);
31 | expect(merged.foo).to.eq(2);
32 | expect(merged).to.not.equal(two);
33 | });
34 |
35 | it('honors false as an explicit blacklisting', function() {
36 | const merged = config.merge(
37 | {plugins: {foo: {}}}, {plugins: {foo: false}},
38 | {plugins: {foo: {}, bar: {}}});
39 |
40 | expect(merged).to.deep.equal({plugins: {foo: false, bar: {}}});
41 | });
42 |
43 | });
44 |
45 | describe('.expand', function() {
46 |
47 | describe('deprecated options', function() {
48 |
49 | it('expands local string browsers', function() {
50 | const context = new Context({browsers: ['chrome']});
51 | return config.expand(context).then(() => {
52 | expect(context.options.plugins['local'].browsers).to.have.members([
53 | 'chrome'
54 | ]);
55 | });
56 | });
57 |
58 | it('expands sauce string browsers', function() {
59 | const context = new Context({browsers: ['linux/firefox']});
60 | return config.expand(context).then(() => {
61 | expect(context.options.plugins['sauce'].browsers).to.have.members([
62 | 'linux/firefox'
63 | ]);
64 | });
65 | });
66 |
67 | it('expands local object browsers', function() {
68 | const context =
69 | new Context({browsers: [{browserName: 'firefox'}]});
70 | return config.expand(context).then(() => {
71 | expect(context.options.plugins['local'].browsers)
72 | .to.deep['have']
73 | .members([{browserName: 'firefox'}]);
74 | });
75 | });
76 |
77 | it('expands sauce object browsers', function() {
78 | const context = new Context(
79 | {browsers: [{browserName: 'safari', platform: 'OS X'}]});
80 | return config.expand(context).then(() => {
81 | expect(context.options.plugins['sauce'].browsers)
82 | .to.deep['have']
83 | .members([{browserName: 'safari', platform: 'OS X'}]);
84 | });
85 | });
86 | });
87 |
88 | });
89 |
90 | });
91 |
--------------------------------------------------------------------------------
/test/unit/gulp.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import * as chai from 'chai';
15 | import * as gulp from 'gulp';
16 | import * as path from 'path';
17 | import * as sinon from 'sinon';
18 |
19 | import {Config} from '../../runner/config';
20 | import {Context} from '../../runner/context';
21 | import * as wctGulp from '../../runner/gulp';
22 | import {Plugin} from '../../runner/plugin';
23 | import * as steps from '../../runner/steps';
24 |
25 | const expect = chai.expect;
26 | chai.use(require('sinon-chai'));
27 |
28 | const FIXTURES = path.resolve(__dirname, '../fixtures/cli');
29 |
30 | describe('gulp', function() {
31 |
32 | let pluginsCalled: string[];
33 | let sandbox: sinon.SinonSandbox;
34 | let orch: gulp.Gulp;
35 | let options: Config;
36 | beforeEach(function() {
37 | orch = new gulp['Gulp']();
38 | wctGulp.init(orch);
39 |
40 | sandbox = sinon.sandbox.create();
41 | sandbox.stub(
42 | steps, 'prepare').callsFake(async(_context: Context): Promise => undefined);
43 | sandbox.stub(steps, 'runTests').callsFake(async (context: Context) => {
44 | options = context.options;
45 | });
46 |
47 | pluginsCalled = [];
48 | sandbox.stub(Plugin.prototype, 'execute').callsFake(async function(context: Context) {
49 | pluginsCalled.push(this.name);
50 | context.options.activeBrowsers.push(
51 | {browserName: 'fake for ' + this.name});
52 | });
53 | });
54 |
55 | afterEach(function() {
56 | sandbox.restore();
57 | });
58 |
59 | async function runGulpTask(name: string) {
60 | await new Promise((resolve, reject) => {
61 | orch.start(name, (error) => error ? reject(error) : resolve());
62 | });
63 | }
64 |
65 | it('honors wcf.conf.js', async () => {
66 | process.chdir(path.join(FIXTURES, 'conf'));
67 | await runGulpTask('wct:sauce');
68 | expect(options.plugins['sauce'].username).to.eq('abc123');
69 | });
70 |
71 | it('prefers wcf.conf.json', async () => {
72 | process.chdir(path.join(FIXTURES, 'conf', 'json'));
73 | await runGulpTask('wct:sauce');
74 | expect(options.plugins['sauce'].username).to.eq('jsonconf');
75 | });
76 |
77 | describe('wct:local', function() {
78 |
79 | it('kicks off local tests', async () => {
80 | await runGulpTask('wct:local');
81 | expect(steps.runTests).to.have.been.calledOnce;
82 | expect(pluginsCalled).to.have.members(['local']);
83 | });
84 |
85 | });
86 |
87 | describe('wct:sauce', function() {
88 |
89 | it('kicks off sauce tests', async () => {
90 | await runGulpTask('wct:sauce');
91 | expect(steps.runTests).to.have.been.calledOnce;
92 | expect(pluginsCalled).to.have.members(['sauce']);
93 | });
94 |
95 | });
96 |
97 | });
98 |
--------------------------------------------------------------------------------
/browser/reporters/title.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import * as util from '../util.js';
12 |
13 | const ARC_OFFSET = 0; // start at the right.
14 | const ARC_WIDTH = 6;
15 |
16 | /**
17 | * A Mocha reporter that updates the document's title and favicon with
18 | * at-a-glance stats.
19 | *
20 | * @param {!Mocha.Runner} runner The runner that is being reported on.
21 | */
22 | export default class Title {
23 | runner: Mocha.IRunner;
24 | constructor(runner: Mocha.IRunner) {
25 | Mocha.reporters.Base.call(this, runner);
26 |
27 | runner.on('test end', this.report.bind(this));
28 | }
29 |
30 | /** Reports current stats via the page title and favicon. */
31 | report() {
32 | this.updateTitle();
33 | this.updateFavicon();
34 | }
35 |
36 | /** Updates the document title with a summary of current stats. */
37 | updateTitle() {
38 | if (this.stats.failures > 0) {
39 | document.title = util.pluralizedStat(this.stats.failures, 'failing');
40 | } else {
41 | document.title = util.pluralizedStat(this.stats.passes, 'passing');
42 | }
43 | }
44 |
45 | /** Updates the document's favicon w/ a summary of current stats. */
46 | updateFavicon() {
47 | const canvas = document.createElement('canvas');
48 | canvas.height = canvas.width = 32;
49 | const context = canvas.getContext('2d');
50 |
51 | const passing = this.stats.passes;
52 | const pending = this.stats.pending;
53 | const failing = this.stats.failures;
54 | const total = Math.max(this.runner.total, passing + pending + failing);
55 | drawFaviconArc(context, total, 0, passing, '#0e9c57');
56 | drawFaviconArc(context, total, passing, pending, '#f3b300');
57 | drawFaviconArc(context, total, pending + passing, failing, '#ff5621');
58 |
59 | this.setFavicon(canvas.toDataURL());
60 | }
61 |
62 | /** Sets the current favicon by URL. */
63 | setFavicon(url: string) {
64 | const current = document.head.querySelector('link[rel="icon"]');
65 | if (current) {
66 | document.head.removeChild(current);
67 | }
68 |
69 | const link = document.createElement('link');
70 | link.rel = 'icon';
71 | link.type = 'image/x-icon';
72 | link.href = url;
73 | link.setAttribute('sizes', '32x32');
74 | document.head.appendChild(link);
75 | }
76 | }
77 |
78 | /**
79 | * Draws an arc for the favicon status, relative to the total number of tests.
80 | */
81 | function drawFaviconArc(
82 | context: CanvasRenderingContext2D, total: number, start: number,
83 | length: number, color: string) {
84 | const arcStart = ARC_OFFSET + Math.PI * 2 * (start / total);
85 | const arcEnd = ARC_OFFSET + Math.PI * 2 * ((start + length) / total);
86 |
87 | context.beginPath();
88 | context.strokeStyle = color;
89 | context.lineWidth = ARC_WIDTH;
90 | context.arc(16, 16, 16 - ARC_WIDTH / 2, arcStart, arcEnd);
91 | context.stroke();
92 | }
93 |
94 | export default interface Title extends Mocha.reporters.Base {}
95 |
--------------------------------------------------------------------------------
/browser/environment.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import * as config from './config.js';
12 | import * as reporters from './reporters.js';
13 | import * as util from './util.js';
14 |
15 | /**
16 | * Loads all environment scripts ...synchronously ...after us.
17 | */
18 | export function loadSync() {
19 | util.debug('Loading environment scripts:');
20 | const a11ySuiteScriptPath = 'web-component-tester/data/a11ySuite.js';
21 | const scripts = config.get('environmentScripts');
22 | const a11ySuiteWillBeLoaded =
23 | window.__generatedByWct || scripts.indexOf(a11ySuiteScriptPath) > -1;
24 |
25 | // We can't inject a11ySuite when running the npm version because it is a
26 | // module-based script that needs `'); // jshint ignore:line
39 | });
40 | util.debug('Environment scripts loaded');
41 |
42 | const imports = config.get('environmentImports');
43 | imports.forEach(function(path) {
44 | const url = util.expandUrl(path, config.get('root'));
45 | util.debug('Loading environment import:', url);
46 | // Synchronous load.
47 | document.write(
48 | ''); // jshint ignore:line
50 | });
51 | util.debug('Environment imports loaded');
52 | }
53 |
54 | /**
55 | * We have some hard dependencies on things that should be loaded via
56 | * `environmentScripts`, so we assert that they're present here; and do any
57 | * post-facto setup.
58 | */
59 | export function ensureDependenciesPresent() {
60 | _ensureMocha();
61 | _checkChai();
62 | }
63 |
64 | function _ensureMocha() {
65 | const Mocha = window.Mocha;
66 | if (!Mocha) {
67 | throw new Error(
68 | 'WCT requires Mocha. Please ensure that it is present in WCT.environmentScripts, or that you load it before loading web-component-tester/browser.js');
69 | }
70 | reporters.injectMocha(Mocha);
71 | // Magic loading of mocha's stylesheet
72 | const mochaPrefix = util.scriptPrefix('mocha.js');
73 | // only load mocha stylesheet for the test runner output
74 | // Not the end of the world, if it doesn't load.
75 | if (mochaPrefix && window.top === window.self) {
76 | util.loadStyle(mochaPrefix + 'mocha.css');
77 | }
78 | }
79 |
80 | function _checkChai() {
81 | if (!window.chai) {
82 | util.debug('Chai not present; not registering shorthands');
83 | return;
84 | }
85 |
86 | window.assert = window.chai.assert;
87 | window.expect = window.chai.expect;
88 | }
89 |
--------------------------------------------------------------------------------
/browser/mocha.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import './mocha/fixture.js';
12 | import './mocha/stub.js';
13 | import './mocha/replace.js';
14 |
15 | import * as config from './config.js';
16 | import {applyExtensions} from './mocha/extend.js';
17 |
18 | // Mocha global helpers, broken out by testing method.
19 | //
20 | // Keys are the method for a particular interface; values are their analog in
21 | // the opposite interface.
22 | const MOCHA_EXPORTS = {
23 | // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/tdd.js
24 | tdd: {
25 | 'setup': '"before"',
26 | 'teardown': '"after"',
27 | 'suiteSetup': '"beforeEach"',
28 | 'suiteTeardown': '"afterEach"',
29 | 'suite': '"describe" or "context"',
30 | 'test': '"it" or "specify"',
31 | },
32 | // https://github.com/visionmedia/mocha/blob/master/lib/interfaces/bdd.js
33 | bdd: {
34 | 'before': '"setup"',
35 | 'after': '"teardown"',
36 | 'beforeEach': '"suiteSetup"',
37 | 'afterEach': '"suiteTeardown"',
38 | 'describe': '"suite"',
39 | 'context': '"suite"',
40 | 'xdescribe': '"suite.skip"',
41 | 'xcontext': '"suite.skip"',
42 | 'it': '"test"',
43 | 'xit': '"test.skip"',
44 | 'specify': '"test"',
45 | 'xspecify': '"test.skip"',
46 | },
47 | };
48 |
49 | /**
50 | * Exposes all Mocha methods up front, configuring and running mocha
51 | * automatically when you call them.
52 | *
53 | * The assumption is that it is a one-off (sub-)suite of tests being run.
54 | */
55 | export function stubInterfaces() {
56 | const keys = Object.keys(MOCHA_EXPORTS) as Array;
57 | keys.forEach(function(ui) {
58 | Object.keys(MOCHA_EXPORTS[ui]).forEach(function(key) {
59 | window[key] = function wrappedMochaFunction() {
60 | _setupMocha(ui, key, MOCHA_EXPORTS[ui][key]);
61 | if (!window[key] || window[key] === wrappedMochaFunction) {
62 | throw new Error('Expected mocha.setup to define ' + key);
63 | }
64 | window[key].apply(window, arguments);
65 | };
66 | });
67 | });
68 | }
69 |
70 | // Whether we've called `mocha.setup`
71 | const _mochaIsSetup = false;
72 |
73 | /**
74 | * @param {string} ui Sets up mocha to run `ui`-style tests.
75 | * @param {string} key The method called that triggered this.
76 | * @param {string} alternate The matching method in the opposite interface.
77 | */
78 | function _setupMocha(ui: 'tdd'|'bdd', key: string, alternate: 'string') {
79 | const mochaOptions = config.get('mochaOptions');
80 | if (mochaOptions.ui && mochaOptions.ui !== ui) {
81 | const message = 'Mixing ' + mochaOptions.ui + ' and ' + ui +
82 | ' Mocha styles is not supported. ' +
83 | 'You called "' + key + '". Did you mean ' + alternate + '?';
84 | throw new Error(message);
85 | }
86 | if (_mochaIsSetup) {
87 | return;
88 | }
89 |
90 | applyExtensions();
91 | mochaOptions.ui = ui;
92 | mocha.setup(mochaOptions); // Note that the reporter is configured in run.js.
93 | }
94 |
--------------------------------------------------------------------------------
/runner/httpbin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | 'use strict';
16 |
17 | import * as bodyParser from 'body-parser';
18 | import * as cleankill from 'cleankill';
19 | import * as express from 'express';
20 | import * as http from 'http';
21 | import * as multer from 'multer';
22 | import * as serverDestroy from 'server-destroy';
23 |
24 | import {findPort} from './port-scanner';
25 | import {Router} from 'express';
26 | export {Router} from 'express';
27 |
28 | export const httpbin: Router = express.Router();
29 |
30 | function capWords(s: string) {
31 | return s.split('-')
32 | .map((word) => word[0].toUpperCase() + word.slice(1))
33 | .join('-');
34 | }
35 |
36 | function formatRequest(req: express.Request) {
37 | const headers = {};
38 | for (const key in req.headers) {
39 | headers[capWords(key)] = req.headers[key];
40 | }
41 | const formatted = {
42 | headers: headers,
43 | url: req.originalUrl,
44 | data: req.body,
45 | files: (req).files,
46 | form: {},
47 | json: {},
48 | };
49 | const contentType =
50 | (headers['Content-Type'] || '').toLowerCase().split(';')[0];
51 | const field = {
52 | 'application/json': 'json',
53 | 'application/x-www-form-urlencoded': 'form',
54 | 'multipart/form-data': 'form'
55 | }[contentType];
56 | if (field) {
57 | formatted[field] = req.body;
58 | }
59 | return formatted;
60 | }
61 |
62 | httpbin.use(bodyParser.urlencoded({extended: false}));
63 | httpbin.use(bodyParser.json());
64 | const storage = multer.memoryStorage();
65 | const upload = multer({storage: storage});
66 | httpbin.use(upload.any());
67 | httpbin.use(bodyParser.text());
68 | httpbin.use(bodyParser.text({type: 'html'}));
69 | httpbin.use(bodyParser.text({type: 'xml'}));
70 |
71 | httpbin.get('/delay/:seconds', function(req, res) {
72 | setTimeout(function() {
73 | res.json(formatRequest(req));
74 | }, (req.params.seconds || 0) * 1000);
75 | });
76 |
77 | httpbin.post('/post', function(req, res) {
78 | res.json(formatRequest(req));
79 | });
80 |
81 | // Running this script directly with `node httpbin.js` will start up a server
82 | // that just serves out /httpbin/...
83 | // Useful for debugging only the httpbin functionality without the rest of
84 | // wct.
85 | async function main() {
86 | const app = express();
87 | const server = http.createServer(app) as serverDestroy.DestroyableServer;
88 |
89 | app.use('/httpbin', httpbin);
90 |
91 |
92 | const port = await findPort([7777, 7000, 8000, 8080, 8888]);
93 |
94 | server.listen(port);
95 | (server).port = port;
96 | serverDestroy(server);
97 | cleankill.onInterrupt(() => {
98 | return new Promise((resolve) => {
99 | server.destroy();
100 | server.on('close', resolve);
101 | });
102 | });
103 |
104 | console.log('Server running at http://localhost:' + port + '/httpbin/');
105 | }
106 |
107 | if (require.main === module) {
108 | main().catch((err) => {
109 | console.error(err);
110 | process.exit(1);
111 | });
112 | }
113 |
--------------------------------------------------------------------------------
/test/integration/setup_test_dir.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import * as rimraf from 'rimraf';
4 |
5 | const baseDir = path.join(__dirname, '..', 'fixtures', 'integration');
6 |
7 | /**
8 | * Sets up the given integration fixture with proper bower components.
9 | *
10 | * For wct to work it needs to be installed in the bower_components directory
11 | * (or, with variants, in each variant directory). So this copies the given
12 | * integration test fixture, then sets up symlinks from
13 | * bower_components/web-component-tester/browser.js to the browser.js of this
14 | * repo. It also makes symlinks for each of wct's bower dependencies into the
15 | * integration tests' bower_components dir.
16 | *
17 | * @param dirname The basename of an integration fixture directory.
18 | * @return A fully resolved path to a copy of the fixture directory with
19 | * a proper bower_components directory.
20 | */
21 | export async function makeProperTestDir(dirname: string) {
22 | const startingDir = path.join(baseDir, dirname);
23 | const tempDir = path.join(baseDir, 'temp');
24 | if (await exists(tempDir)) {
25 | await new Promise((resolve, reject) => {
26 | rimraf(tempDir, (err) => err ? reject(err) : resolve());
27 | });
28 | }
29 | fs.mkdirSync(tempDir);
30 |
31 | // copy dir
32 | const pathToTestDir = await copyDir(startingDir, tempDir);
33 |
34 | fs.mkdirSync(path.join(pathToTestDir, 'node_modules'));
35 | fs.mkdirSync(
36 | path.join(pathToTestDir, 'node_modules', 'web-component-tester'));
37 |
38 | // set up symlinks into component dirs for browser.js, data/, and wct's
39 | // dependencies (like mocha, sinon, etc)
40 | const componentsDirs = new Set(['bower_components']);
41 | for (const baseFile of fs.readdirSync(startingDir)) {
42 | if (/^bower_components(-|$)/.test(baseFile)) {
43 | componentsDirs.add(baseFile);
44 | }
45 | }
46 |
47 | for (const baseComponentsDir of componentsDirs) {
48 | const componentsDir = path.join(pathToTestDir, baseComponentsDir);
49 | if (!await exists(componentsDir)) {
50 | fs.mkdirSync(componentsDir);
51 | }
52 | // all of wct's bower deps should be present in the project under tests'
53 | // components dir
54 | const bowerDeps =
55 | fs.readdirSync(path.join(__dirname, '../../bower_components'));
56 | for (const baseFile of bowerDeps) {
57 | fs.symlinkSync(
58 | path.join('../../../../../../bower_components', baseFile),
59 | path.join(componentsDir, baseFile));
60 | }
61 | // Also set up a web-component-tester dir with symlinks into our own
62 | // client-side files.
63 | const wctDir = path.join(componentsDir, 'web-component-tester');
64 | fs.mkdirSync(wctDir);
65 | fs.symlinkSync(
66 | '../../../../../../../browser.js', path.join(wctDir, 'browser.js'),
67 | 'file');
68 | fs.symlinkSync(
69 | '../../../../../../../package.json', path.join(wctDir, 'package.json'),
70 | 'file');
71 | fs.symlinkSync(
72 | '../../../../../../../data', path.join(wctDir, 'data'), 'dir');
73 | }
74 |
75 | return pathToTestDir;
76 | }
77 |
78 | async function copyDir(from: string, to: string) {
79 | const newDir = path.join(to, path.basename(from));
80 | fs.mkdirSync(newDir);
81 | for (const baseFile of fs.readdirSync(from)) {
82 | const file = path.join(from, baseFile);
83 | if (fs.statSync(file).isDirectory()) {
84 | await copyDir(file, newDir);
85 | } else {
86 | const newFile = path.join(newDir, baseFile);
87 | fs.writeFileSync(newFile, fs.readFileSync(file));
88 | }
89 | }
90 | return newDir;
91 | }
92 |
93 | async function exists(fn: string) {
94 | return new Promise((resolve) => fs.stat(fn, (err) => resolve(!err)));
95 | }
96 |
--------------------------------------------------------------------------------
/test/unit/paths.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import {expect} from 'chai';
15 | import * as path from 'path';
16 |
17 | import * as paths from '../../runner/paths';
18 |
19 | describe('paths', function() {
20 |
21 | describe('.expand', function() {
22 | const baseDir = path.resolve(__dirname, '../fixtures/paths');
23 |
24 | async function expectExpands(patterns: string[], expected: string[]) {
25 | const actual = await paths.expand(baseDir, patterns);
26 |
27 | // for non-POSIX support
28 | expected = expected.map((str) => str.replace(/\//g, path.sep));
29 | expect(actual).to.have.members(expected);
30 | }
31 |
32 | it('is ok with an empty list', async () => {
33 | await expectExpands([], []);
34 | });
35 |
36 | it('ignores explicit files that are missing', async () => {
37 | await expectExpands(['404.js'], []);
38 | await expectExpands(['404.js', 'foo.html'], ['foo.html']);
39 | });
40 |
41 | it('does not expand explicit files', async () => {
42 | await expectExpands(['foo.js'], ['foo.js']);
43 | await expectExpands(['foo.html'], ['foo.html']);
44 | await expectExpands(['foo.js', 'foo.html'], ['foo.js', 'foo.html']);
45 | });
46 |
47 | it('expands directories into their files', async () => {
48 | await expectExpands(['foo'], ['foo/one.js', 'foo/two.html']);
49 | await expectExpands(['foo/'], ['foo/one.js', 'foo/two.html']);
50 | });
51 |
52 | it('expands directories into index.html when present', async () => {
53 | await expectExpands(['bar'], ['bar/index.html']);
54 | await expectExpands(['bar/'], ['bar/index.html']);
55 | });
56 |
57 | it('expands directories recursively, honoring all rules', async () => {
58 | await expectExpands(['baz'], [
59 | 'baz/a/fizz.html',
60 | 'baz/b/index.html',
61 | 'baz/a.html',
62 | 'baz/b.js',
63 | ]);
64 | });
65 |
66 | it('accepts globs for explicit file matches', async () => {
67 | await expectExpands(['baz/*.js'], ['baz/b.js']);
68 | await expectExpands(['baz/*.html'], ['baz/a.html']);
69 | await expectExpands(['baz/**/*.js'], [
70 | 'baz/b/deep/stuff.js',
71 | 'baz/b/one.js',
72 | 'baz/b.js',
73 | ]);
74 | await expectExpands(['baz/**/*.html'], [
75 | 'baz/a/fizz.html',
76 | 'baz/b/deep/index.html',
77 | 'baz/b/deep/stuff.html',
78 | 'baz/b/index.html',
79 | 'baz/a.html',
80 | ]);
81 | });
82 |
83 | it('accepts globs for directories, honoring directory behavior',
84 | async () => {
85 | await expectExpands(['*'], [
86 | 'bar/index.html',
87 | 'baz/a/fizz.html',
88 | 'baz/b/index.html',
89 | 'baz/a.html',
90 | 'baz/b.js',
91 | 'foo/one.js',
92 | 'foo/two.html',
93 | 'foo.html',
94 | 'foo.js',
95 | ]);
96 | await expectExpands(['baz/*'], [
97 | 'baz/a/fizz.html',
98 | 'baz/b/index.html',
99 | 'baz/a.html',
100 | 'baz/b.js',
101 | ]);
102 | });
103 |
104 | it('deduplicates', async () => {
105 | await expectExpands(['bar/a.js', 'bar/*.js', 'bar', 'bar/*.html'], [
106 | 'bar/a.js',
107 | 'bar/index.html',
108 | 'bar/index.js',
109 | ]);
110 | });
111 | });
112 | });
113 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-component-tester",
3 | "version": "6.5.0",
4 | "--private-wct--": {
5 | "client-side-version-range": "4 - 6 || ^6.0.0-prerelease.1",
6 | "wct-browser-legacy-version-range": "0.0.1-pre.1 || ^1.0.0"
7 | },
8 | "description": "web-component-tester makes testing your web components a breeze!",
9 | "keywords": [
10 | "browser",
11 | "grunt",
12 | "gruntplugin",
13 | "gulp",
14 | "polymer",
15 | "test",
16 | "testing",
17 | "web component",
18 | "web"
19 | ],
20 | "homepage": "https://github.com/Polymer/web-component-tester",
21 | "bugs": "https://github.com/Polymer/web-component-tester/issues",
22 | "license": "BSD-3-Clause",
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/Polymer/web-component-tester.git"
26 | },
27 | "main": "runner.js",
28 | "bin": {
29 | "wct": "./bin/wct",
30 | "wct-st": "./bin/wct-st"
31 | },
32 | "files": [
33 | "bin/",
34 | "data/",
35 | "runner/",
36 | "scripts/",
37 | "tasks/",
38 | ".bowerrc",
39 | "bower.json",
40 | "browser.js",
41 | "browser.js.map",
42 | "package.json",
43 | "LICENSE",
44 | "README.md",
45 | "runner.js"
46 | ],
47 | "scripts": {
48 | "lint": "gulp lint",
49 | "build": "tsc && gulp build",
50 | "test": "tsc && gulp test",
51 | "prepublishOnly": "gulp prepublish",
52 | "test:watch": "watch 'gulp test:unit' runner/ browser/ bin/ test/ tasks/",
53 | "format": "find runner test | grep '\\.js$\\|\\.ts$' | xargs ./node_modules/.bin/clang-format --style=file -i"
54 | },
55 | "dependencies": {
56 | "@polymer/sinonjs": "^1.14.1",
57 | "@polymer/test-fixture": "^0.0.3",
58 | "@webcomponents/webcomponentsjs": "^1.0.7",
59 | "accessibility-developer-tools": "^2.12.0",
60 | "async": "^2.4.1",
61 | "body-parser": "^1.17.2",
62 | "chai": "^4.0.2",
63 | "chalk": "^1.1.3",
64 | "cleankill": "^2.0.0",
65 | "express": "^4.15.3",
66 | "findup-sync": "^1.0.0",
67 | "glob": "^7.1.2",
68 | "lodash": "^3.10.1",
69 | "mocha": "^3.4.2",
70 | "multer": "^1.3.0",
71 | "nomnom": "^1.8.1",
72 | "polyserve": "^0.23.0",
73 | "promisify-node": "^0.4.0",
74 | "resolve": "^1.3.3",
75 | "semver": "^5.3.0",
76 | "send": "^0.11.1",
77 | "server-destroy": "^1.0.1",
78 | "sinon": "^2.3.5",
79 | "sinon-chai": "^2.10.0",
80 | "socket.io": "^2.0.3",
81 | "stacky": "^1.3.1",
82 | "wd": "^1.2.0"
83 | },
84 | "optionalDependencies": {
85 | "update-notifier": "^2.2.0",
86 | "wct-local": "^2.1.0",
87 | "wct-sauce": "^2.0.0"
88 | },
89 | "devDependencies": {
90 | "@types/body-parser": "0.0.33",
91 | "@types/chai": "^3.4.34",
92 | "@types/chalk": "^0.4.31",
93 | "@types/express": "^4.0.33",
94 | "@types/express-serve-static-core": "^4.0.39",
95 | "@types/glob": "^5.0.30",
96 | "@types/grunt": "^0.4.20",
97 | "@types/gulp": "^3.8.8",
98 | "@types/lodash": "^4.14.38",
99 | "@types/mime": "0.0.29",
100 | "@types/minimatch": "^2.0.29",
101 | "@types/mocha": "^2.2.32",
102 | "@types/multer": "0.0.32",
103 | "@types/node": "^8.0.0",
104 | "@types/nomnom": "0.0.28",
105 | "@types/rimraf": "0.0.28",
106 | "@types/semver": "^5.3.30",
107 | "@types/sinon": "^1.16.31",
108 | "@types/sinon-chai": "^2.7.27",
109 | "@types/socket.io": "^1.4.27",
110 | "bower": "^1.7.9",
111 | "clang-format": "^1.0.43",
112 | "depcheck": "^0.6.3",
113 | "grunt": "^0.4.5",
114 | "gulp": "^3.8.8",
115 | "gulp-bower": "0.0.13",
116 | "gulp-concat": "^2.6.1",
117 | "gulp-spawn-mocha": "^3.1.0",
118 | "gulp-tslint": "^8.1.2",
119 | "gulp-typescript": "^3.1.2",
120 | "lazypipe": "^1.0.1",
121 | "rimraf": "^2.5.4",
122 | "rollup": "^0.25.1",
123 | "run-sequence": "^1.0.1",
124 | "tslint": "^5.7.0",
125 | "typescript": "^2.1.4",
126 | "watch": "^0.18.0"
127 | },
128 | "engines": {
129 | "node": ">= 6.0"
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/browser/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | /**
12 | * This file is the entry point into `web-component-tester`'s browser client.
13 | */
14 | // Registers a bunch of globals:
15 | import './environment/helpers.js';
16 |
17 | import ChildRunner from './childrunner.js';
18 | import CLISocket from './clisocket.js';
19 | import * as config from './config.js';
20 | import * as environment from './environment.js';
21 | import * as errors from './environment/errors.js';
22 | import * as mocha from './mocha.js';
23 | import * as reporters from './reporters.js';
24 | import MultiReporter from './reporters/multi.js';
25 | import * as suites from './suites.js';
26 | import * as util from './util.js';
27 |
28 | // You can configure WCT before it has loaded by assigning your custom
29 | // configuration to the global `WCT`.
30 | config.setup(window.WCT as any as config.Config);
31 |
32 | // Maybe some day we'll expose WCT as a module to whatever module registry you
33 | // are using (aka the UMD approach), or as an es6 module.
34 | const WCT = window.WCT = {
35 | // A generic place to hang data about the current suite. This object is
36 | // reported
37 | // back via the `sub-suite-start` and `sub-suite-end` events.
38 | share: {},
39 | // Until then, we get to rely on it to expose parent runners to their
40 | // children.
41 | _ChildRunner: ChildRunner,
42 | _reporter: undefined as any, // assigned below
43 | _config: config._config,
44 |
45 | // Public API
46 |
47 | /**
48 | * Loads suites of tests, supporting both `.js` and `.html` files.
49 | *
50 | * @param {!Array.} files The files to load.
51 | */
52 | loadSuites: suites.loadSuites,
53 | };
54 |
55 | // Load Process
56 |
57 | errors.listenForErrors();
58 | mocha.stubInterfaces();
59 | environment.loadSync();
60 |
61 | // Give any scripts on the page a chance to declare tests and muck with things.
62 | document.addEventListener('DOMContentLoaded', function() {
63 | util.debug('DOMContentLoaded');
64 |
65 | environment.ensureDependenciesPresent();
66 |
67 | // We need the socket built prior to building its reporter.
68 | CLISocket.init(function(error, socket) {
69 | if (error)
70 | throw error;
71 |
72 | // Are we a child of another run?
73 | const current = ChildRunner.current();
74 | const parent = current && current.parentScope.WCT._reporter;
75 | util.debug('parentReporter:', parent);
76 |
77 | const childSuites = suites.activeChildSuites();
78 | const reportersToUse = reporters.determineReporters(socket, parent);
79 | // +1 for any local tests.
80 | const reporter =
81 | new MultiReporter(childSuites.length + 1, reportersToUse, parent);
82 | WCT._reporter = reporter; // For environment/compatibility.js
83 |
84 | // We need the reporter so that we can report errors during load.
85 | suites.loadJsSuites(reporter, function(error) {
86 | // Let our parent know that we're about to start the tests.
87 | if (current)
88 | current.ready(error);
89 | if (error)
90 | throw error;
91 |
92 | // Emit any errors we've encountered up til now
93 | errors.globalErrors.forEach(function onError(error) {
94 | reporter.emitOutOfBandTest('Test Suite Initialization', error);
95 | });
96 |
97 | suites.runSuites(reporter, childSuites, function(error) {
98 | // Make sure to let our parent know that we're done.
99 | if (current)
100 | current.done();
101 | if (error)
102 | throw error;
103 | });
104 | });
105 | });
106 | });
107 |
--------------------------------------------------------------------------------
/test/unit/grunt.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import * as chai from 'chai';
15 | import * as grunt from 'grunt';
16 | import * as _ from 'lodash';
17 | import * as path from 'path';
18 | import * as sinon from 'sinon';
19 |
20 | import {Context} from '../../runner/context';
21 | import * as steps from '../../runner/steps';
22 |
23 |
24 | const wctLocalBrowsers = require('wct-local/lib/browsers');
25 | const expect = chai.expect;
26 | chai.use(require('sinon-chai'));
27 |
28 | const LOCAL_BROWSERS = {
29 | aurora: {browserName: 'aurora', version: '1'},
30 | canary: {browserName: 'canary', version: '2'},
31 | chrome: {browserName: 'chrome', version: '3'},
32 | firefox: {browserName: 'firefox', version: '4'},
33 | };
34 |
35 | describe('grunt', function() {
36 |
37 | // Sinon doesn't stub process.env very well.
38 | let origEnv: any, origArgv: any;
39 | beforeEach(function() {
40 | origEnv = _.clone(process.env);
41 | origArgv = process.argv;
42 | });
43 | afterEach(function() {
44 | _.assign(process.env, origEnv);
45 | _.difference(_.keys(process.env), _.keys(origEnv)).forEach(function(key) {
46 | delete process.env[key];
47 | });
48 | process.argv = origArgv;
49 | });
50 |
51 | before(function() {
52 | grunt.initConfig({
53 | 'wct-test': {
54 | 'passthrough': {
55 | options: {foo: 1, bar: 'asdf'},
56 | },
57 | 'override': {
58 | options: {sauce: {username: '--real-sauce--'}},
59 | },
60 | },
61 | });
62 | grunt.loadTasks(path.resolve(__dirname, '../../tasks'));
63 | });
64 |
65 | async function runTask(task: string) {
66 | await new Promise((resolve, reject) => {
67 | grunt.task['options']({error: reject, done: resolve});
68 | grunt.task.run('wct-test:' + task)['start']();
69 | });
70 | // We shouldn't error before hitting it.
71 | expect(steps.runTests).to.have.been.calledOnce;
72 | return <{args: [Context]}>steps.runTests['getCall'](0);
73 | }
74 |
75 | describe('wct-test', function() {
76 |
77 | let sandbox: sinon.SinonSandbox;
78 | beforeEach(function() {
79 | sandbox = sinon.sandbox.create();
80 | sandbox.stub(steps, 'prepare')
81 | .callsFake(
82 | async(_context: Context): Promise => undefined);
83 |
84 | sandbox.stub(wctLocalBrowsers, 'detect').callsFake(async () => LOCAL_BROWSERS);
85 | sandbox.stub(wctLocalBrowsers, 'supported').callsFake(() => _.keys(LOCAL_BROWSERS));
86 |
87 | process.chdir(path.resolve(__dirname, '../fixtures/cli/standard'));
88 | });
89 |
90 | afterEach(function() {
91 | sandbox.restore();
92 | });
93 |
94 | describe('with a passing suite', function() {
95 |
96 | beforeEach(function() {
97 | sandbox.stub(steps, 'runTests').callsFake(async(): Promise => undefined);
98 | });
99 |
100 | it('passes configuration through', async () => {
101 | const call = await runTask('passthrough');
102 | expect(call.args[0].options).to.include({foo: 1, bar: 'asdf'});
103 | });
104 | });
105 |
106 | describe('with a failing suite', function() {
107 | beforeEach(function() {
108 | sandbox.stub(steps, 'runTests').callsFake(async () => {
109 | throw 'failures';
110 | });
111 | });
112 |
113 | it('passes errors out', async () => {
114 | try {
115 | await runTask('passthrough');
116 | } catch (error) {
117 | return; // All's well!
118 | }
119 | throw new Error('Expected runTask to fail!');
120 | });
121 | });
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/runner/plugin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | import * as _ from 'lodash';
16 | import * as path from 'path';
17 |
18 | import {Config} from './config';
19 | import {Context} from './context';
20 |
21 | // Plugin module names can be prefixed by the following:
22 | const PREFIXES = [
23 | 'web-component-tester-',
24 | 'wct-',
25 | ];
26 |
27 | export interface Metadata {}
28 |
29 | /**
30 | * A WCT plugin. This constructor is private. Plugins can be retrieved via
31 | * `Plugin.get`.
32 | */
33 | export class Plugin {
34 | name: string;
35 | cliConfig: Config;
36 | packageName: string;
37 | metadata: Metadata;
38 | constructor(packageName: string, metadata: Metadata) {
39 | this.packageName = packageName;
40 | this.metadata = metadata;
41 | this.name = Plugin.shortName(packageName);
42 |
43 | this.cliConfig = this.metadata['cli-options'] || {};
44 | }
45 |
46 | /**
47 | * @param {!Context} context The context that this plugin should be evaluated
48 | * within.
49 | */
50 | async execute(context: Context): Promise {
51 | try {
52 | const plugin = require(this.packageName);
53 | plugin(context, context.pluginOptions(this.name), this);
54 | } catch (error) {
55 | throw `Failed to load plugin "${this.name}": ${error}`;
56 | }
57 | }
58 |
59 | /**
60 | * Retrieves a plugin by shorthand or module name (loading it as necessary).
61 | *
62 | * @param {string} name
63 | */
64 | static async get(name: string): Promise {
65 | const shortName = Plugin.shortName(name);
66 | if (_loadedPlugins[shortName]) {
67 | return _loadedPlugins[shortName];
68 | }
69 |
70 | const names = [shortName].concat(PREFIXES.map((p) => p + shortName));
71 | const loaded = _.compact(names.map(_tryLoadPluginPackage));
72 | if (loaded.length > 1) {
73 | const prettyNames = loaded.map((p) => p.packageName).join(' ');
74 | throw `Loaded conflicting WCT plugin packages: ${prettyNames}`;
75 | }
76 | if (loaded.length < 1) {
77 | throw `Could not find WCT plugin named "${name}"`;
78 | }
79 |
80 | return loaded[0];
81 | }
82 |
83 | /**
84 | * @param {string} name
85 | * @return {string} The short form of `name`.
86 | */
87 | static shortName(name: string) {
88 | for (const prefix of PREFIXES) {
89 | if (name.indexOf(prefix) === 0) {
90 | return name.substr(prefix.length);
91 | }
92 | }
93 | return name;
94 | }
95 |
96 | // HACK(rictic): Makes es6 style imports happy, so that we can do, e.g.
97 | // import {Plugin} from './plugin';
98 | static Plugin = Plugin;
99 | }
100 |
101 | // Plugin Loading
102 |
103 | // We maintain an identity map of plugins, keyed by short name.
104 | const _loadedPlugins: {[name: string]: Plugin} = {};
105 |
106 | /**
107 | * @param {string} packageName Attempts to load a package as a WCT plugin.
108 | * @return {Plugin}
109 | */
110 | function _tryLoadPluginPackage(packageName: string) {
111 | let packageInfo: Object;
112 | try {
113 | packageInfo = require(path.join(packageName, 'package.json'));
114 | } catch (error) {
115 | if (error.code !== 'MODULE_NOT_FOUND') {
116 | console.log(error);
117 | }
118 | return null;
119 | }
120 |
121 | // Plugins must have a (truthy) wct-plugin field.
122 | if (!packageInfo['wct-plugin']) {
123 | return null;
124 | }
125 | // Allow {"wct-plugin": true} as a shorthand.
126 | const metadata =
127 | _.isObject(packageInfo['wct-plugin']) ? packageInfo['wct-plugin'] : {};
128 |
129 | return new Plugin(packageName, metadata);
130 | }
131 |
132 |
133 | module.exports = Plugin;
134 |
--------------------------------------------------------------------------------
/browser/config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import ChildRunner from './childrunner.js';
12 | import * as util from './util.js';
13 |
14 | export interface Config {
15 | /**
16 | * `.js` scripts to be loaded (synchronously) before WCT starts in earnest.
17 | *
18 | * Paths are relative to `scriptPrefix`.
19 | */
20 | environmentScripts: string[];
21 | environmentImports: string[];
22 | /** Absolute root for client scripts. Detected in `setup()` if not set. */
23 | root: null|string;
24 | /** By default, we wait for any web component frameworks to load. */
25 | waitForFrameworks: boolean;
26 | /**
27 | * Alternate callback for waiting for tests.
28 | * `this` for the callback will be the window currently running tests.
29 | */
30 | waitFor: null|Function;
31 | /** How many `.html` suites that can be concurrently loaded & run. */
32 | numConcurrentSuites: number;
33 | /** Whether `console.error` should be treated as a test failure. */
34 | trackConsoleError: boolean;
35 | /** Configuration passed to mocha.setup. */
36 | mochaOptions: MochaSetupOptions;
37 | /** Whether WCT should emit (extremely verbose) debugging log messages. */
38 | verbose: boolean;
39 | }
40 |
41 | /**
42 | * The global configuration state for WCT's browser client.
43 | */
44 | export let _config: Config = {
45 | environmentScripts: !!window.__wctUseNpm ?
46 | [
47 | 'stacky/browser.js', 'async/lib/async.js', 'lodash/index.js',
48 | 'mocha/mocha.js', 'chai/chai.js', '@polymer/sinonjs/sinon.js',
49 | 'sinon-chai/lib/sinon-chai.js',
50 | 'accessibility-developer-tools/dist/js/axs_testing.js',
51 | '@polymer/test-fixture/test-fixture.js'
52 | ] :
53 | [
54 | 'stacky/browser.js', 'async/lib/async.js', 'lodash/lodash.js',
55 | 'mocha/mocha.js', 'chai/chai.js', 'sinonjs/sinon.js',
56 | 'sinon-chai/lib/sinon-chai.js',
57 | 'accessibility-developer-tools/dist/js/axs_testing.js'
58 | ],
59 |
60 | environmentImports: !!window.__wctUseNpm ? [] :
61 | ['test-fixture/test-fixture.html'],
62 | root: null as null | string,
63 | waitForFrameworks: true,
64 | waitFor: null as null | Function,
65 | numConcurrentSuites: 1,
66 | trackConsoleError: true,
67 | mochaOptions: {timeout: 10 * 1000},
68 | verbose: false,
69 | };
70 |
71 | /**
72 | * Merges initial `options` into WCT's global configuration.
73 | *
74 | * @param {Object} options The options to merge. See `browser/config.js` for a
75 | * reference.
76 | */
77 | export function setup(options: Config) {
78 | const childRunner = ChildRunner.current();
79 | if (childRunner) {
80 | _deepMerge(_config, childRunner.parentScope.WCT._config);
81 | // But do not force the mocha UI
82 | delete _config.mochaOptions.ui;
83 | }
84 |
85 | if (options && typeof options === 'object') {
86 | _deepMerge(_config, options);
87 | }
88 |
89 | if (!_config.root) {
90 | // Sibling dependencies.
91 | const root = util.scriptPrefix('browser.js');
92 | _config.root = util.basePath(root.substr(0, root.length - 1));
93 | if (!_config.root) {
94 | throw new Error(
95 | 'Unable to detect root URL for WCT sources. Please set WCT.root before including browser.js');
96 | }
97 | }
98 | }
99 |
100 | /**
101 | * Retrieves a configuration value.
102 | */
103 | export function get(key: K): Config[K] {
104 | return _config[key];
105 | }
106 |
107 | // Internal
108 | function _deepMerge(target: Partial, source: Config) {
109 | Object.keys(source).forEach(function(key) {
110 | if (target[key] !== null && typeof target[key] === 'object' &&
111 | !Array.isArray(target[key])) {
112 | _deepMerge(target[key], source[key]);
113 | } else {
114 | target[key] = source[key];
115 | }
116 | });
117 | }
118 |
--------------------------------------------------------------------------------
/browser/mocha/replace.ts:
--------------------------------------------------------------------------------
1 | import {extendInterfaces} from './extend.js';
2 |
3 | // replacement map stores what should be
4 | let replacements = {};
5 | let replaceTeardownAttached = false;
6 |
7 | /**
8 | * replace
9 | *
10 | * The replace addon allows the tester to replace all usages of one element with
11 | * another element within all Polymer elements created within the time span of
12 | * the test. Usage example:
13 | *
14 | * beforeEach(function() {
15 | * replace('x-foo').with('x-fake-foo');
16 | * });
17 | *
18 | * All annotations and attributes will be set on the placement element the way
19 | * they were set for the original element.
20 | */
21 | extendInterfaces('replace', function(_context, teardown) {
22 | return function replace(oldTagName: string) {
23 | return {
24 | with: function(tagName: string) {
25 | // Standardizes our replacements map
26 | oldTagName = oldTagName.toLowerCase();
27 | tagName = tagName.toLowerCase();
28 |
29 | replacements[oldTagName] = tagName;
30 |
31 | // If the function is already a stub, restore it to original
32 | if ((document.importNode as any).isSinonProxy) {
33 | return;
34 | }
35 |
36 | if (!window.Polymer.Element) {
37 | window.Polymer.Element = function() {};
38 | window.Polymer.Element.prototype._stampTemplate = function() {};
39 | }
40 |
41 | // Keep a reference to the original `document.importNode`
42 | // implementation for later:
43 | const originalImportNode = document.importNode;
44 |
45 | // Use Sinon to stub `document.ImportNode`:
46 | sinon.stub(
47 | document, 'importNode', function(origContent: any, deep: boolean) {
48 | const templateClone = document.createElement('template');
49 | const content = templateClone.content;
50 | const inertDoc = content.ownerDocument;
51 |
52 | // imports node from inertDoc which holds inert nodes.
53 | templateClone.content.appendChild(
54 | inertDoc.importNode(origContent, true));
55 |
56 | // optional arguments are not optional on IE.
57 | const nodeIterator = document.createNodeIterator(
58 | content, NodeFilter.SHOW_ELEMENT, null, true);
59 | let node;
60 |
61 | // Traverses the tree. A recently-replaced node will be put next,
62 | // so if a node is replaced, it will be checked if it needs to be
63 | // replaced again.
64 | while (node = nodeIterator.nextNode() as Element) {
65 | let currentTagName = node.tagName.toLowerCase();
66 |
67 | if (replacements.hasOwnProperty(currentTagName)) {
68 | currentTagName = replacements[currentTagName];
69 |
70 | // find the final tag name.
71 | while (replacements[currentTagName]) {
72 | currentTagName = replacements[currentTagName];
73 | }
74 |
75 | // Create a replacement:
76 | const replacement = document.createElement(currentTagName);
77 |
78 | // For all attributes in the original node..
79 | for (let index = 0; index < node.attributes.length; ++index) {
80 | // Set that attribute on the replacement:
81 | replacement.setAttribute(
82 | node.attributes[index].name,
83 | node.attributes[index].value);
84 | }
85 |
86 | // Replace the original node with the replacement node:
87 | node.parentNode.replaceChild(replacement, node);
88 | }
89 | }
90 |
91 | return originalImportNode.call(this, content, deep);
92 | });
93 |
94 | if (!replaceTeardownAttached) {
95 | // After each test...
96 | teardown(function() {
97 | replaceTeardownAttached = true;
98 | // Restore the stubbed version of `document.importNode`:
99 | const documentImportNode = document.importNode as any;
100 | if (documentImportNode.isSinonProxy) {
101 | documentImportNode.restore();
102 | }
103 |
104 | // Empty the replacement map
105 | replacements = {};
106 | });
107 | }
108 | }
109 | };
110 | };
111 | });
112 |
--------------------------------------------------------------------------------
/browser/reporters/console.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import * as util from '../util.js';
12 |
13 | // We capture console events when running tests; so make sure we have a
14 | // reference to the original one.
15 | const console = window.console;
16 |
17 | const FONT =
18 | ';font: normal 13px "Roboto", "Helvetica Neue", "Helvetica", sans-serif;';
19 | const STYLES = {
20 | plain: FONT,
21 | suite: 'color: #5c6bc0' + FONT,
22 | test: FONT,
23 | passing: 'color: #259b24' + FONT,
24 | pending: 'color: #e65100' + FONT,
25 | failing: 'color: #c41411' + FONT,
26 | stack: 'color: #c41411',
27 | results: FONT + 'font-size: 16px',
28 | };
29 |
30 | // I don't think we can feature detect this one...
31 | const userAgent = navigator.userAgent.toLowerCase();
32 | const CAN_STYLE_LOG = userAgent.match('firefox') || userAgent.match('webkit');
33 | const CAN_STYLE_GROUP = userAgent.match('webkit');
34 | // Track the indent for faked `console.group`
35 | let logIndent = '';
36 |
37 | function log(text: string, style?: keyof typeof STYLES) {
38 | text = text.split('\n')
39 | .map(function(l) {
40 | return logIndent + l;
41 | })
42 | .join('\n');
43 | if (CAN_STYLE_LOG) {
44 | console.log('%c' + text, STYLES[style] || STYLES.plain);
45 | } else {
46 | console.log(text);
47 | }
48 | }
49 |
50 | function logGroup(text: string, style?: keyof typeof STYLES) {
51 | if (CAN_STYLE_GROUP) {
52 | console.group('%c' + text, STYLES[style] || STYLES.plain);
53 | } else if (console.group) {
54 | console.group(text);
55 | } else {
56 | logIndent = logIndent + ' ';
57 | log(text, style);
58 | }
59 | }
60 |
61 | function logGroupEnd() {
62 | if (console.groupEnd) {
63 | console.groupEnd();
64 | } else {
65 | logIndent = logIndent.substr(0, logIndent.length - 2);
66 | }
67 | }
68 |
69 | function logException(error: Error) {
70 | log(error.stack || error.message || (error + ''), 'stack');
71 | }
72 |
73 | /**
74 | * A Mocha reporter that logs results out to the web `console`.
75 | */
76 | export default class Console {
77 | /**
78 | * @param runner The runner that is being reported on.
79 | */
80 | constructor(runner: Mocha.IRunner) {
81 | Mocha.reporters.Base.call(this, runner);
82 |
83 | runner.on('suite', function(suite: Mocha.ISuite) {
84 | if (suite.root) {
85 | return;
86 | }
87 | logGroup(suite.title, 'suite');
88 | }.bind(this));
89 |
90 | runner.on('suite end', function(suite: Mocha.ISuite) {
91 | if (suite.root) {
92 | return;
93 | }
94 | logGroupEnd();
95 | }.bind(this));
96 |
97 | runner.on('test', function(test: Mocha.ITest) {
98 | logGroup(test.title, 'test');
99 | }.bind(this));
100 |
101 | runner.on('pending', function(test: Mocha.ITest) {
102 | logGroup(test.title, 'pending');
103 | }.bind(this));
104 |
105 | runner.on('fail', function(_test: Mocha.ITest, error: any) {
106 | logException(error);
107 | }.bind(this));
108 |
109 | runner.on('test end', function(_test: Mocha.ITest) {
110 | logGroupEnd();
111 | }.bind(this));
112 |
113 | runner.on('end', this.logSummary.bind(this));
114 | }
115 |
116 | /** Prints out a final summary of test results. */
117 | logSummary() {
118 | logGroup('Test Results', 'results');
119 |
120 | if (this.stats.failures > 0) {
121 | log(util.pluralizedStat(this.stats.failures, 'failing'), 'failing');
122 | }
123 | if (this.stats.pending > 0) {
124 | log(util.pluralizedStat(this.stats.pending, 'pending'), 'pending');
125 | }
126 | log(util.pluralizedStat(this.stats.passes, 'passing'));
127 |
128 | if (!this.stats.failures) {
129 | log('test suite passed', 'passing');
130 | }
131 | log('Evaluated ' + this.stats.tests + ' tests in ' +
132 | (this.stats as any).duration + 'ms.');
133 | logGroupEnd();
134 | }
135 | }
136 |
137 | export default interface Console extends Mocha.reporters.Base {}
138 |
--------------------------------------------------------------------------------
/runner/cli.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | import * as chalk from 'chalk';
16 | import * as events from 'events';
17 | import * as _ from 'lodash';
18 |
19 | import {CliReporter} from './clireporter';
20 | import * as config from './config';
21 | import {Context} from './context';
22 | import {Plugin} from './plugin';
23 | import {test} from './test';
24 |
25 | const PACKAGE_INFO = require('../package.json');
26 | const noopNotifier = {
27 | notify: () => {}
28 | };
29 | let updateNotifier = noopNotifier;
30 |
31 | (function() {
32 | try {
33 | updateNotifier = require('update-notifier')({pkg: PACKAGE_INFO});
34 | } catch (error) {
35 | // S'ok if we don't have update-notifier. It's optional.
36 | }
37 | })();
38 |
39 | export async function run(
40 | _env: any, args: string[], output: NodeJS.WritableStream): Promise {
41 | await wrapResult(output, _run(args, output));
42 | }
43 |
44 | async function _run(args: string[], output: NodeJS.WritableStream) {
45 | // If the "--version" or "-V" flag is ever present, just print
46 | // the current version. Useful for globally installed CLIs.
47 | if (args.includes('--version') || args.includes('-V')) {
48 | output.write(`${PACKAGE_INFO.version}\n`);
49 | return Promise.resolve();
50 | }
51 |
52 | // Options parsing is a two phase affair. First, we need an initial set of
53 | // configuration so that we know which plugins to load, etc:
54 | let options = config.preparseArgs(args) as config.Config;
55 | // Depends on values from the initial merge:
56 | options = config.merge(options, {
57 | output: output,
58 | ttyOutput: !process.env.CI && output['isTTY'] && !options.simpleOutput,
59 | });
60 | const context = new Context(options);
61 |
62 | if (options.skipUpdateCheck) {
63 | updateNotifier = noopNotifier;
64 | }
65 |
66 | // `parseArgs` merges any new configuration into `context.options`.
67 | await config.parseArgs(context, args);
68 | await test(context);
69 | }
70 |
71 | // Note that we're cheating horribly here. Ideally all of this logic is within
72 | // wct-sauce. The trouble is that we also want WCT's configuration lookup logic,
73 | // and that's not (yet) cleanly exposed.
74 | export async function runSauceTunnel(
75 | _env: any, args: string[], output: NodeJS.WritableStream): Promise {
76 | await wrapResult(output, _runSauceTunnel(args, output));
77 | }
78 |
79 | async function _runSauceTunnel(args: string[], output: NodeJS.WritableStream) {
80 | const cmdOptions = config.preparseArgs(args) as config.Config;
81 | const context = new Context(cmdOptions);
82 |
83 | const diskOptions = context.options;
84 | const baseOptions: config.Config =
85 | (diskOptions.plugins && diskOptions.plugins['sauce']) ||
86 | diskOptions.sauce || {};
87 |
88 | const plugin = await Plugin.get('sauce');
89 | const parser = require('nomnom');
90 | parser.script('wct-st');
91 | parser.options(_.omit(plugin.cliConfig, 'browsers', 'tunnelId'));
92 | const options = _.merge(baseOptions, parser.parse(args));
93 |
94 | const wctSauce = require('wct-sauce');
95 | wctSauce.expandOptions(options);
96 |
97 | const emitter = new events.EventEmitter();
98 | new CliReporter(emitter, output, {});
99 | const tunnelId = await new Promise((resolve, reject) => {
100 | wctSauce.startTunnel(
101 | options, emitter,
102 | (error: any, tunnelId: string) =>
103 | error ? reject(error) : resolve(tunnelId));
104 | });
105 |
106 | output.write('\n');
107 | output.write(
108 | 'The tunnel will remain active while this process is running.\n');
109 | output.write(
110 | 'To use this tunnel for other WCT runs, export the following:\n');
111 | output.write('\n');
112 | output.write(chalk.cyan('export SAUCE_TUNNEL_ID=' + tunnelId) + '\n');
113 | }
114 |
115 | async function wrapResult(
116 | output: NodeJS.WritableStream, promise: Promise) {
117 | let error: any;
118 | try {
119 | await promise;
120 | } catch (e) {
121 | error = e;
122 | }
123 |
124 | if (!process.env.CI) {
125 | updateNotifier.notify();
126 | }
127 |
128 | if (error) {
129 | output.write('\n');
130 | output.write(chalk.red(error) + '\n');
131 | output.write('\n');
132 | throw error;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/data/a11ySuite.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9 | */
10 |
11 | var a11ySuiteExport;
12 |
13 | (function(Mocha, chai, axs) {
14 |
15 | Object.keys(Mocha.interfaces).forEach(function(iface) {
16 | var orig = Mocha.interfaces[iface];
17 |
18 | Mocha.interfaces[iface] = function(suite) {
19 | orig.apply(this, arguments);
20 |
21 | var Suite = Mocha.Suite;
22 | var Test = Mocha.Test;
23 |
24 | suite.on('pre-require', function(context, file, mocha) {
25 |
26 | /**
27 | * Runs the Chrome Accessibility Developer Tools Audit against a test-fixture
28 | *
29 | * @param {String} fixtureId ID of the fixture element in the document to use
30 | * @param {Array?} ignoredRules Array of rules to ignore for this suite
31 | * @param {Function?} beforeEach Function to be called before each test to ensure proper setup
32 | */
33 | a11ySuiteExport = context.a11ySuite = function(fixtureId, ignoredRules, beforeEach) {
34 | // capture a reference to the fixture element early
35 | var fixtureElement = document.getElementById(fixtureId);
36 | if (!fixtureElement) {
37 | return;
38 | }
39 |
40 | // build an audit config to disable certain ignorable tests
41 | var axsConfig = new axs.AuditConfiguration();
42 | axsConfig.scope = document.body;
43 | axsConfig.showUnsupportedRulesWarning = false;
44 | axsConfig.auditRulesToIgnore = ignoredRules;
45 |
46 | // build mocha suite
47 | var a11ySuite = Suite.create(suite, 'A11y Audit - Fixture: ' + fixtureId);
48 |
49 | // override the `eachTest` function to hackily create the tests
50 | //
51 | // eachTest is called right before test runs to calculate the total
52 | // number of tests
53 | a11ySuite.eachTest = function() {
54 | // instantiate fixture
55 | fixtureElement.create();
56 |
57 | // Make sure lazy-loaded dom is ready (eg )
58 | Polymer.dom.flush();
59 |
60 | // If we have a beforeEach function, call it
61 | if (beforeEach) {
62 | beforeEach();
63 | }
64 |
65 | // run audit
66 | var auditResults = axs.Audit.run(axsConfig);
67 |
68 | // create tests for audit results
69 | auditResults.forEach(function(result, index) {
70 | // only show applicable tests
71 | if (result.result !== 'NA') {
72 | var title = result.rule.heading;
73 | // fail test if audit result is FAIL
74 | var error = result.result === 'FAIL' ? axs.Audit.accessibilityErrorMessage(result) : null;
75 | var test = new Test(title, function() {
76 | if (error) {
77 | throw new Error(error);
78 | }
79 | });
80 | test.file = file;
81 | a11ySuite.addTest(test);
82 | }
83 | });
84 |
85 | // teardown fixture
86 | fixtureElement.restore();
87 |
88 | suite.eachTest.apply(a11ySuite, arguments);
89 | this.eachTest = suite.eachTest;
90 | };
91 |
92 | return a11ySuite;
93 | };
94 | });
95 | };
96 | });
97 |
98 | chai.use(function(chai, util) {
99 | var Assertion = chai.Assertion;
100 |
101 | // assert
102 | chai.assert.a11yLabel = function(node, exp, msg){
103 | new Assertion(node).to.have.a11yLabel(exp, msg);
104 | };
105 |
106 | // expect / should
107 | Assertion.addMethod('a11yLabel', function(str, msg) {
108 | if (msg) {
109 | util.flag(this, 'message', msg);
110 | }
111 |
112 | var node = this._obj;
113 |
114 | // obj must be a Node
115 | new Assertion(node).to.be.instanceOf(Node);
116 |
117 | // vind the text alternative with the help of accessibility dev tools
118 | var textAlternative = axs.properties.findTextAlternatives(node, {});
119 |
120 | this.assert(
121 | textAlternative === str,
122 | 'expected #{this} to have text alternative #{exp} but got #{act}',
123 | 'expected #{this} to not have text alternative #{act}',
124 | str,
125 | textAlternative,
126 | true
127 | );
128 | });
129 | });
130 | })(window.Mocha, window.chai, window.axs);
131 |
--------------------------------------------------------------------------------
/wct-browser-legacy/a11ySuite.js:
--------------------------------------------------------------------------------
1 | import * as polymerDom from '../@polymer/polymer/lib/legacy/polymer.dom.js';
2 | const Polymer = { dom: polymerDom };
3 | export {a11ySuiteExport as a11ySuite};
4 |
5 | // wct-browser-legacy/a11ySuite.js is a generated file. Source is in web-component-tester/data/a11ySuite.js
6 |
7 | /**
8 | * @license
9 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
10 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
11 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
12 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
13 | * Code distributed by Google as part of the polymer project is also
14 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
15 | */
16 |
17 | var a11ySuiteExport;
18 |
19 | (function(Mocha, chai, axs) {
20 |
21 | Object.keys(Mocha.interfaces).forEach(function(iface) {
22 | var orig = Mocha.interfaces[iface];
23 |
24 | Mocha.interfaces[iface] = function(suite) {
25 | orig.apply(this, arguments);
26 |
27 | var Suite = Mocha.Suite;
28 | var Test = Mocha.Test;
29 |
30 | suite.on('pre-require', function(context, file, mocha) {
31 |
32 | /**
33 | * Runs the Chrome Accessibility Developer Tools Audit against a test-fixture
34 | *
35 | * @param {String} fixtureId ID of the fixture element in the document to use
36 | * @param {Array?} ignoredRules Array of rules to ignore for this suite
37 | * @param {Function?} beforeEach Function to be called before each test to ensure proper setup
38 | */
39 | a11ySuiteExport = context.a11ySuite = function(fixtureId, ignoredRules, beforeEach) {
40 | // capture a reference to the fixture element early
41 | var fixtureElement = document.getElementById(fixtureId);
42 | if (!fixtureElement) {
43 | return;
44 | }
45 |
46 | // build an audit config to disable certain ignorable tests
47 | var axsConfig = new axs.AuditConfiguration();
48 | axsConfig.scope = document.body;
49 | axsConfig.showUnsupportedRulesWarning = false;
50 | axsConfig.auditRulesToIgnore = ignoredRules;
51 |
52 | // build mocha suite
53 | var a11ySuite = Suite.create(suite, 'A11y Audit - Fixture: ' + fixtureId);
54 |
55 | // override the `eachTest` function to hackily create the tests
56 | //
57 | // eachTest is called right before test runs to calculate the total
58 | // number of tests
59 | a11ySuite.eachTest = function() {
60 | // instantiate fixture
61 | fixtureElement.create();
62 |
63 | // Make sure lazy-loaded dom is ready (eg )
64 | Polymer.dom.flush();
65 |
66 | // If we have a beforeEach function, call it
67 | if (beforeEach) {
68 | beforeEach();
69 | }
70 |
71 | // run audit
72 | var auditResults = axs.Audit.run(axsConfig);
73 |
74 | // create tests for audit results
75 | auditResults.forEach(function(result, index) {
76 | // only show applicable tests
77 | if (result.result !== 'NA') {
78 | var title = result.rule.heading;
79 | // fail test if audit result is FAIL
80 | var error = result.result === 'FAIL' ? axs.Audit.accessibilityErrorMessage(result) : null;
81 | var test = new Test(title, function() {
82 | if (error) {
83 | throw new Error(error);
84 | }
85 | });
86 | test.file = file;
87 | a11ySuite.addTest(test);
88 | }
89 | });
90 |
91 | // teardown fixture
92 | fixtureElement.restore();
93 |
94 | suite.eachTest.apply(a11ySuite, arguments);
95 | this.eachTest = suite.eachTest;
96 | };
97 |
98 | return a11ySuite;
99 | };
100 | });
101 | };
102 | });
103 |
104 | chai.use(function(chai, util) {
105 | var Assertion = chai.Assertion;
106 |
107 | // assert
108 | chai.assert.a11yLabel = function(node, exp, msg){
109 | new Assertion(node).to.have.a11yLabel(exp, msg);
110 | };
111 |
112 | // expect / should
113 | Assertion.addMethod('a11yLabel', function(str, msg) {
114 | if (msg) {
115 | util.flag(this, 'message', msg);
116 | }
117 |
118 | var node = this._obj;
119 |
120 | // obj must be a Node
121 | new Assertion(node).to.be.instanceOf(Node);
122 |
123 | // vind the text alternative with the help of accessibility dev tools
124 | var textAlternative = axs.properties.findTextAlternatives(node, {});
125 |
126 | this.assert(
127 | textAlternative === str,
128 | 'expected #{this} to have text alternative #{exp} but got #{act}',
129 | 'expected #{this} to not have text alternative #{act}',
130 | str,
131 | textAlternative,
132 | true
133 | );
134 | });
135 | });
136 | })(window.Mocha, window.chai, window.axs);
137 |
--------------------------------------------------------------------------------
/browser/clisocket.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 |
12 | import ChildRunner from './childrunner.js';
13 | import * as util from './util.js';
14 |
15 | const SOCKETIO_ENDPOINT =
16 | window.location.protocol + '//' + window.location.host;
17 | const SOCKETIO_LIBRARY = SOCKETIO_ENDPOINT + '/socket.io/socket.io.js';
18 |
19 | /**
20 | * A socket for communication between the CLI and browser runners.
21 | *
22 | * @param {string} browserId An ID generated by the CLI runner.
23 | * @param {!io.Socket} socket The socket.io `Socket` to communicate over.
24 | */
25 | export default class CLISocket {
26 | private readonly socket: SocketIO.Socket;
27 | private readonly browserId: string;
28 | constructor(browserId: string, socket: SocketIO.Socket) {
29 | this.browserId = browserId;
30 | this.socket = socket;
31 | }
32 |
33 | /**
34 | * @param {!Mocha.Runner} runner The Mocha `Runner` to observe, reporting
35 | * interesting events back to the CLI runner.
36 | */
37 | observe(runner: Mocha.IRunner) {
38 | this.emitEvent('browser-start', {
39 | url: window.location.toString(),
40 | });
41 |
42 | // We only emit a subset of events that we care about, and follow a more
43 | // general event format that is hopefully applicable to test runners beyond
44 | // mocha.
45 | //
46 | // For all possible mocha events, see:
47 | // https://github.com/visionmedia/mocha/blob/master/lib/runner.js#L36
48 | runner.on('test', (test: Mocha.IRunnable) => {
49 | this.emitEvent('test-start', {test: getTitles(test)});
50 | });
51 |
52 | runner.on('test end', (test: Mocha.IRunnable) => {
53 | this.emitEvent('test-end', {
54 | state: getState(test),
55 | test: getTitles(test),
56 | duration: (test as any).duration,
57 | error: (test as any).err,
58 | });
59 | });
60 |
61 | runner.on('fail', (test, err) => {
62 | // fail the test run if we catch errors outside of a test function
63 | if (test.type !== 'test') {
64 | this.emitEvent(
65 | 'browser-fail',
66 | 'Error thrown outside of test function: ' + err.stack);
67 | }
68 | });
69 |
70 | runner.on('childRunner start', (childRunner) => {
71 | this.emitEvent('sub-suite-start', childRunner.share);
72 | });
73 |
74 | runner.on('childRunner end', (childRunner) => {
75 | this.emitEvent('sub-suite-end', childRunner.share);
76 | });
77 |
78 | runner.on('end', () => {
79 | this.emitEvent('browser-end');
80 | });
81 | }
82 |
83 | /**
84 | * @param {string} event The name of the event to fire.
85 | * @param {*} data Additional data to pass with the event.
86 | */
87 | emitEvent(event: string, data?: any) {
88 | this.socket.emit('client-event', {
89 | browserId: this.browserId,
90 | event: event,
91 | data: data,
92 | });
93 | }
94 |
95 | /**
96 | * Builds a `CLISocket` if we are within a CLI-run environment; short-circuits
97 | * otherwise.
98 | *
99 | * @param {function(*, CLISocket)} done Node-style callback.
100 | */
101 | static init(done: (error?: any, socket?: CLISocket) => void) {
102 | const browserId = util.getParam('cli_browser_id');
103 | if (!browserId)
104 | return done();
105 | // Only fire up the socket for root runners.
106 | if (ChildRunner.current())
107 | return done();
108 |
109 | util.loadScript(SOCKETIO_LIBRARY, function(error: any) {
110 | if (error)
111 | return done(error);
112 |
113 | const socket = io(SOCKETIO_ENDPOINT);
114 | socket.on('error', function(error?: any) {
115 | socket.off();
116 | done(error);
117 | });
118 |
119 | socket.on('connect', function() {
120 | socket.off();
121 | done(null, new CLISocket(browserId, socket as any));
122 | });
123 | });
124 | }
125 | }
126 |
127 | // Misc Utility
128 |
129 | /**
130 | * @param {!Mocha.Runnable} runnable The test or suite to extract titles from.
131 | * @return {!Array.} The titles of the runnable and its parents.
132 | */
133 | function getTitles(runnable: Mocha.IRunnable) {
134 | const titles = [];
135 | while (runnable && !runnable.root && runnable.title) {
136 | titles.unshift(runnable.title);
137 | runnable = runnable.parent as any;
138 | }
139 | return titles;
140 | }
141 |
142 | /**
143 | * @param {!Mocha.Runnable} runnable
144 | * @return {string}
145 | */
146 | function getState(runnable: Mocha.IRunnable) {
147 | if (runnable.state === 'passed') {
148 | return 'passing';
149 | } else if (runnable.state === 'failed') {
150 | return 'failing';
151 | } else if (runnable.pending) {
152 | return 'pending';
153 | } else {
154 | return 'unknown';
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/test/unit/context.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import * as chai from 'chai';
15 | import * as sinon from 'sinon';
16 | import * as sinonChai from 'sinon-chai';
17 |
18 | import {Context} from '../../runner/context';
19 | import {Plugin} from '../../runner/plugin';
20 |
21 | const expect = chai.expect;
22 | chai.use(sinonChai);
23 |
24 | describe('Context', () => {
25 |
26 | let sandbox: sinon.SinonSandbox;
27 | beforeEach(() => {
28 | sandbox = sinon.sandbox.create();
29 | });
30 |
31 | afterEach(() => {
32 | sandbox.restore();
33 | });
34 |
35 | describe('.plugins', () => {
36 |
37 | it('excludes plugins with a falsy config', async () => {
38 | const context = new Context({plugins: {local: false, sauce: {}}});
39 | const stub = sandbox.stub(Plugin, 'get').callsFake((name: string) => {
40 | return Promise.resolve(name);
41 | });
42 |
43 | const plugins = await context.plugins();
44 | expect(stub).to.have.been.calledOnce;
45 | expect(stub).to.have.been.calledWith('sauce');
46 | expect(plugins).to.have.members(['sauce']);
47 | });
48 |
49 | it('excludes plugins disabled: true', async () => {
50 | const context =
51 | new Context({plugins: {local: {}, sauce: {disabled: true}}});
52 | const stub = sandbox.stub(Plugin, 'get').callsFake((name: string) => {
53 | return Promise.resolve(name);
54 | });
55 |
56 | const plugins = await context.plugins();
57 | expect(stub).to.have.been.calledOnce;
58 | expect(stub).to.have.been.calledWith('local');
59 | expect(plugins).to.have.members(['local']);
60 | });
61 |
62 | describe('hook handlers with non-callback second argument', async () => {
63 | it('are passed the "done" callback function instead of the argument passed to emitHook',
64 | async () => {
65 | const context = new Context();
66 | context.hook('foo', function(arg1: any, done: () => void) {
67 | expect(arg1).to.eq('hookArg');
68 | done();
69 | });
70 | await context.emitHook('foo', 'hookArg');
71 | });
72 | });
73 |
74 | describe('hook handlers written to call callbacks', () => {
75 | it('passes additional arguments through', async () => {
76 | const context = new Context();
77 | context.hook(
78 | 'foo',
79 | (arg1: string, arg2: number, hookDone: (err?: any) => void) => {
80 | expect(arg1).to.eq('one');
81 | expect(arg2).to.eq(2);
82 | hookDone();
83 | });
84 |
85 | // Tests the promise form of emitHook.
86 | await context.emitHook('foo', 'one', 2);
87 |
88 | // Tests the callback form of emitHook.
89 | const error = await new Promise((resolve) => {
90 | context.emitHook('foo', 'one', 2, resolve);
91 | });
92 | expect(error).to.not.be.ok;
93 | });
94 |
95 | it('halts on error', async () => {
96 | const context = new Context();
97 | context.hook('bar', function(hookDone: (err?: any) => void) {
98 | hookDone('nope');
99 | });
100 |
101 | // Tests the promise form of emitHook.
102 | try {
103 | await context.emitHook('bar');
104 | throw new Error('emitHook should have thrown');
105 | } catch (error) {
106 | expect(error).to.eq('nope');
107 | }
108 |
109 | // Tests the callback form of emitHook.
110 | const error = await new Promise((resolve) => {
111 | context.emitHook('bar', resolve);
112 | });
113 | expect(error).to.eq('nope');
114 | });
115 | });
116 |
117 | describe('hooks handlers written to return promises', () => {
118 | it('passes additional arguments through', async () => {
119 | const context = new Context();
120 | context.hook('foo', async function(arg1: any, arg2: any) {
121 | expect(arg1).to.eq('one');
122 | expect(arg2).to.eq(2);
123 | });
124 |
125 | await context.emitHook('foo', 'one', 2);
126 | const error = await new Promise((resolve) => {
127 | context.emitHook('foo', 'one', 2, resolve);
128 | });
129 | expect(error).to.not.be.ok;
130 | });
131 |
132 | it('halts on error', async () => {
133 | const context = new Context();
134 | context.hook('bar', async () => {
135 | throw 'nope';
136 | });
137 |
138 | // Tests the promise form of emitHook.
139 | try {
140 | await context.emitHook('bar');
141 | throw new Error('emitHook should have thrown');
142 | } catch (error) {
143 | expect(error).to.eq('nope');
144 | }
145 |
146 | // Tests the callback form of emitHook.
147 | const error = await new Promise((resolve) => {
148 | context.emitHook('bar', resolve);
149 | });
150 | expect(error).to.eq('nope');
151 | });
152 | });
153 | });
154 | });
155 |
--------------------------------------------------------------------------------
/browser/childrunner.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import * as util from './util.js';
12 |
13 | // TODO(thedeeno): Consider renaming subsuite. IIRC, childRunner is entirely
14 | // distinct from mocha suite, which tripped me up badly when trying to add
15 | // plugin support. Perhaps something like 'batch', or 'bundle'. Something that
16 | // has no mocha correlate. This may also eliminate the need for root/non-root
17 | // suite distinctions.
18 |
19 | export interface SharedState {}
20 |
21 | /**
22 | * A Mocha suite (or suites) run within a child iframe, but reported as if they
23 | * are part of the current context.
24 | */
25 | export default class ChildRunner {
26 | private url: string;
27 | parentScope: Window;
28 | private state: 'initializing'|'loading'|'complete';
29 | private iframe?: HTMLIFrameElement;
30 | private onRunComplete: (error?: any) => void;
31 | private timeoutId: undefined|number;
32 | private share: SharedState;
33 |
34 | constructor(url: string, parentScope: Window) {
35 | const urlBits = util.parseUrl(url);
36 | util.mergeParams(
37 | urlBits.params, util.getParams(parentScope.location.search));
38 | delete urlBits.params.cli_browser_id;
39 |
40 | this.url = urlBits.base + util.paramsToQuery(urlBits.params);
41 | this.parentScope = parentScope;
42 |
43 | this.state = 'initializing';
44 | }
45 |
46 | // ChildRunners get a pretty generous load timeout by default.
47 | static loadTimeout = 60000;
48 |
49 | // We can't maintain properties on iframe elements in Firefox/Safari/???, so
50 | // we track childRunners by URL.
51 | static _byUrl: {[url: string]: undefined|ChildRunner} = {};
52 |
53 | /**
54 | * @return {ChildRunner} The `ChildRunner` that was registered for this
55 | * window.
56 | */
57 | static current(): ChildRunner {
58 | return ChildRunner.get(window);
59 | }
60 |
61 | /**
62 | * @param {!Window} target A window to find the ChildRunner of.
63 | * @param {boolean} traversal Whether this is a traversal from a child window.
64 | * @return {ChildRunner} The `ChildRunner` that was registered for `target`.
65 | */
66 | static get(target: Window, traversal?: boolean): ChildRunner {
67 | const childRunner = ChildRunner._byUrl[target.location.href];
68 | if (childRunner) {
69 | return childRunner;
70 | }
71 | if (window.parent === window) { // Top window.
72 | if (traversal) {
73 | console.warn(
74 | 'Subsuite loaded but was never registered. This most likely is due to wonky history behavior. Reloading...');
75 | window.location.reload();
76 | }
77 | return null;
78 | }
79 | // Otherwise, traverse.
80 | return window.parent.WCT._ChildRunner.get(target, true);
81 | }
82 |
83 | /**
84 | * Loads and runs the subsuite.
85 | *
86 | * @param {function} done Node-style callback.
87 | */
88 | run(done: (error?: any) => void) {
89 | util.debug('ChildRunner#run', this.url);
90 | this.state = 'loading';
91 | this.onRunComplete = done;
92 |
93 | this.iframe = document.createElement('iframe');
94 | this.iframe.src = this.url;
95 | this.iframe.classList.add('subsuite');
96 |
97 | let container = document.getElementById('subsuites');
98 | if (!container) {
99 | container = document.createElement('div');
100 | container.id = 'subsuites';
101 | document.body.appendChild(container);
102 | }
103 | container.appendChild(this.iframe);
104 |
105 | // let the iframe expand the URL for us.
106 | this.url = this.iframe.src;
107 | ChildRunner._byUrl[this.url] = this;
108 |
109 | this.timeoutId = setTimeout(
110 | this.loaded.bind(this, new Error('Timed out loading ' + this.url)),
111 | ChildRunner.loadTimeout);
112 |
113 | this.iframe.addEventListener(
114 | 'error',
115 | this.loaded.bind(
116 | this, new Error('Failed to load document ' + this.url)));
117 |
118 | this.iframe.contentWindow.addEventListener(
119 | 'DOMContentLoaded', this.loaded.bind(this, null));
120 | }
121 |
122 | /**
123 | * Called when the sub suite's iframe has loaded (or errored during load).
124 | *
125 | * @param {*} error The error that occured, if any.
126 | */
127 | loaded(error: any) {
128 | util.debug('ChildRunner#loaded', this.url, error);
129 |
130 | // Not all targets have WCT loaded (compatiblity mode)
131 | if (this.iframe.contentWindow.WCT) {
132 | this.share = this.iframe.contentWindow.WCT.share;
133 | }
134 |
135 | if (error) {
136 | this.signalRunComplete(error);
137 | this.done();
138 | }
139 | }
140 |
141 | /**
142 | * Called in mocha/run.js when all dependencies have loaded, and the child is
143 | * ready to start running tests
144 | *
145 | * @param {*} error The error that occured, if any.
146 | */
147 | ready(error?: any) {
148 | util.debug('ChildRunner#ready', this.url, error);
149 | if (this.timeoutId) {
150 | clearTimeout(this.timeoutId);
151 | }
152 | if (error) {
153 | this.signalRunComplete(error);
154 | this.done();
155 | }
156 | }
157 |
158 | /**
159 | * Called when the sub suite's tests are complete, so that it can clean up.
160 | */
161 | done() {
162 | util.debug('ChildRunner#done', this.url, arguments);
163 |
164 | // make sure to clear that timeout
165 | this.ready();
166 | this.signalRunComplete();
167 |
168 | if (!this.iframe)
169 | return;
170 | // Be safe and avoid potential browser crashes when logic attempts to
171 | // interact with the removed iframe.
172 | setTimeout(function() {
173 | this.iframe.parentNode.removeChild(this.iframe);
174 | this.iframe = null;
175 | }.bind(this), 1);
176 | }
177 |
178 | signalRunComplete(error?: any) {
179 | if (!this.onRunComplete)
180 | return;
181 | this.state = 'complete';
182 | this.onRunComplete(error);
183 | this.onRunComplete = null;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/runner/steps.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 | import * as http from 'http';
15 | import * as _ from 'lodash';
16 | import * as socketIO from 'socket.io';
17 |
18 | import {BrowserRunner} from './browserrunner';
19 | import * as config from './config';
20 | import {Context} from './context';
21 | import {Plugin} from './plugin';
22 | import {webserver} from './webserver';
23 |
24 | interface ClientMessage {
25 | browserId: number;
26 | event: string;
27 | data: T;
28 | }
29 |
30 | // Steps (& Hooks)
31 |
32 | export async function setupOverrides(context: Context): Promise {
33 | if (context.options.registerHooks) {
34 | context.options.registerHooks(context);
35 | }
36 | }
37 |
38 | export async function loadPlugins(context: Context): Promise {
39 | context.emit('log:debug', 'step: loadPlugins');
40 |
41 | const plugins = await context.plugins();
42 |
43 | // built in quasi-plugin.
44 | webserver(context);
45 |
46 | // Actual plugins.
47 | await Promise.all(plugins.map((plugin) => plugin.execute(context)));
48 | return plugins;
49 | }
50 |
51 | export async function configure(context: Context): Promise {
52 | context.emit('log:debug', 'step: configure');
53 | const options = context.options;
54 |
55 | await config.expand(context);
56 |
57 | // Note that we trigger the configure hook _after_ filling in the `options`
58 | // object.
59 | //
60 | // If you want to modify options prior to this; do it during plugin init.
61 | await context.emitHook('configure');
62 |
63 | // Even if the options don't validate; useful debugging info.
64 | const cleanOptions = _.omit(options, 'output');
65 | context.emit('log:debug', 'configuration:', cleanOptions);
66 |
67 | await config.validate(options);
68 | }
69 |
70 | /**
71 | * The prepare step is where a lot of the runner's initialization occurs. This
72 | * is also typically where a plugin will want to spin up any long-running
73 | * process it requires.
74 | *
75 | * Note that some "plugins" are also built directly into WCT (webserver).
76 | */
77 | export async function prepare(context: Context): Promise {
78 | await context.emitHook('prepare');
79 | }
80 |
81 | export async function runTests(context: Context): Promise {
82 | context.emit('log:debug', 'step: runTests');
83 |
84 | const result = runBrowsers(context);
85 | const runners = result.runners;
86 | context._testRunners = runners;
87 |
88 | context._socketIOServers = context._httpServers.map((httpServer) => {
89 | const socketIOServer = socketIO(httpServer);
90 | socketIOServer.on('connection', function(socket) {
91 | context.emit('log:debug', 'Test client opened sideband socket');
92 | socket.on('client-event', function(data: ClientMessage) {
93 | const runner = runners[data.browserId];
94 | if (!runner) {
95 | throw new Error(
96 | `Unable to find browser runner for ` +
97 | `browser with id: ${data.browserId}`);
98 | }
99 | runner.onEvent(data.event, data.data);
100 | });
101 | });
102 | return socketIOServer;
103 | });
104 |
105 | await result.completionPromise;
106 | }
107 |
108 | export function cancelTests(context: Context): void {
109 | if (!context._testRunners) {
110 | return;
111 | }
112 | context._testRunners.forEach(function(tr) {
113 | tr.quit();
114 | });
115 | }
116 |
117 | // Helpers
118 |
119 | function runBrowsers(context: Context) {
120 | const options = context.options;
121 | const numActiveBrowsers = options.activeBrowsers.length;
122 | if (numActiveBrowsers === 0) {
123 | throw new Error('No browsers configured to run');
124 | }
125 |
126 | // TODO(nevir): validate browser definitions.
127 |
128 | // Up the socket limit so that we can maintain more active requests.
129 | // TODO(nevir): We should be queueing the browsers above some limit too.
130 | http.globalAgent.maxSockets =
131 | Math.max(http.globalAgent.maxSockets, numActiveBrowsers * 2);
132 |
133 | context.emit('run-start', options);
134 |
135 | const errors: any[] = [];
136 |
137 | const promises: Promise[] = [];
138 |
139 | const runners: BrowserRunner[] = [];
140 | let id = 0;
141 | for (const originalBrowserDef of options.activeBrowsers) {
142 | let waitFor: undefined|Promise = undefined;
143 | for (const server of options.webserver._servers) {
144 | // Needed by both `BrowserRunner` and `CliReporter`.
145 | const browserDef = _.clone(originalBrowserDef);
146 | browserDef.id = id++;
147 | browserDef.variant = server.variant;
148 | _.defaultsDeep(browserDef, options.browserOptions);
149 |
150 | const runner =
151 | new BrowserRunner(context, browserDef, options, server.url, waitFor);
152 | promises.push(runner.donePromise.then(
153 | () => {
154 | context.emit('log:debug', browserDef, 'BrowserRunner complete');
155 | },
156 | (error) => {
157 | context.emit('log:debug', browserDef, 'BrowserRunner complete');
158 | errors.push(error);
159 | }));
160 | runners.push(runner);
161 | if (browserDef.browserName === 'safari') {
162 | // Control to Safari must be serialized. We can't launch two instances
163 | // simultaneously, because security lol.
164 | // https://webkit.org/blog/6900/webdriver-support-in-safari-10/
165 | waitFor = runner.donePromise.catch(() => {
166 | // The next runner doesn't care about errors, just wants to know when
167 | // it can start.
168 | return undefined;
169 | });
170 | }
171 | }
172 | }
173 |
174 | return {
175 | runners,
176 | completionPromise: (async function() {
177 | await Promise.all(promises);
178 | const error = errors.length > 0 ? _.union(errors).join(', ') : null;
179 | context.emit('run-end', error);
180 | // TODO(nevir): Better rationalize run-end and hook.
181 | await context.emitHook('cleanup');
182 |
183 | if (error) {
184 | throw new Error(error);
185 | }
186 | }())
187 | };
188 | }
189 |
--------------------------------------------------------------------------------
/browser/suites.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 | import ChildRunner from './childrunner.js';
12 | import * as config from './config.js';
13 | import MultiReporter from './reporters/multi.js';
14 | import * as util from './util.js';
15 |
16 | export let htmlSuites: string[] = [];
17 | export let jsSuites: string[] = [];
18 |
19 | // We process grep ourselves to avoid loading suites that will be filtered.
20 | let GREP = util.getParam('grep');
21 | // work around mocha bug (https://github.com/mochajs/mocha/issues/2070)
22 | if (GREP) {
23 | GREP = GREP.replace(/\\\./g, '.');
24 | }
25 |
26 | /**
27 | * Loads suites of tests, supporting both `.js` and `.html` files.
28 | *
29 | * @param files The files to load.
30 | */
31 | export function loadSuites(files: string[]) {
32 | files.forEach(function(file) {
33 | if (/\.js(\?.*)?$/.test(file)) {
34 | jsSuites.push(file);
35 | } else if (/\.html(\?.*)?$/.test(file)) {
36 | htmlSuites.push(file);
37 | } else {
38 | throw new Error('Unknown resource type: ' + file);
39 | }
40 | });
41 | }
42 |
43 | /**
44 | * @return The child suites that should be loaded, ignoring
45 | * those that would not match `GREP`.
46 | */
47 | export function activeChildSuites(): string[] {
48 | let subsuites = htmlSuites;
49 | if (GREP) {
50 | const cleanSubsuites = [];
51 | for (let i = 0, subsuite; subsuite = subsuites[i]; i++) {
52 | if (GREP.indexOf(util.cleanLocation(subsuite)) !== -1) {
53 | cleanSubsuites.push(subsuite);
54 | }
55 | }
56 | subsuites = cleanSubsuites;
57 | }
58 | return subsuites;
59 | }
60 |
61 | /**
62 | * Loads all `.js` sources requested by the current suite.
63 | */
64 | export function loadJsSuites(
65 | _reporter: MultiReporter, done: (error: Error) => void) {
66 | util.debug('loadJsSuites', jsSuites);
67 |
68 | const loaders = jsSuites.map(function(file) {
69 | // We only support `.js` dependencies for now.
70 | return util.loadScript.bind(util, file);
71 | });
72 |
73 | util.parallel(loaders, done);
74 | }
75 |
76 | export function runSuites(
77 | reporter: MultiReporter, childSuites: string[],
78 | done: (error?: any) => void) {
79 | util.debug('runSuites');
80 |
81 | const suiteRunners: Array<(next: () => void) => void> = [
82 | // Run the local tests (if any) first, not stopping on error;
83 | _runMocha.bind(null, reporter),
84 | ];
85 |
86 | // As well as any sub suites. Again, don't stop on error.
87 | childSuites.forEach(function(file) {
88 | suiteRunners.push(function(next) {
89 | const childRunner = new ChildRunner(file, window);
90 | reporter.emit('childRunner start', childRunner);
91 | childRunner.run(function(error) {
92 | reporter.emit('childRunner end', childRunner);
93 | if (error)
94 | reporter.emitOutOfBandTest(file, error);
95 | next();
96 | });
97 | });
98 | });
99 |
100 | util.parallel(
101 | suiteRunners, config.get('numConcurrentSuites'), function(error) {
102 | reporter.done();
103 | done(error);
104 | });
105 | }
106 |
107 | /**
108 | * Kicks off a mocha run, waiting for frameworks to load if necessary.
109 | *
110 | * @param {!MultiReporter} reporter Where to send Mocha's events.
111 | * @param {function} done A callback fired, _no error is passed_.
112 | */
113 | function _runMocha(reporter: MultiReporter, done: () => void, waited: boolean) {
114 | if (config.get('waitForFrameworks') && !waited) {
115 | const waitFor =
116 | (config.get('waitFor') || util.whenFrameworksReady).bind(window);
117 | waitFor(function() {
118 | _fixCustomElements();
119 | _runMocha(reporter, done, true);
120 | });
121 | return;
122 | }
123 | util.debug('_runMocha');
124 | const mocha = window.mocha;
125 | const Mocha = window.Mocha;
126 |
127 | mocha.reporter(reporter.childReporter(window.location));
128 | mocha.suite.title = reporter.suiteTitle(window.location);
129 | mocha.grep(GREP);
130 |
131 | // We can't use `mocha.run` because it bashes over grep, invert, and friends.
132 | // See https://github.com/visionmedia/mocha/blob/master/support/tail.js#L137
133 | const runner = Mocha.prototype.run.call(mocha, function(_error: any) {
134 | if (document.getElementById('mocha')) {
135 | Mocha.utils.highlightTags('code');
136 | }
137 | done(); // We ignore the Mocha failure count.
138 | });
139 |
140 | // Mocha's default `onerror` handling strips the stack (to support really old
141 | // browsers). We upgrade this to get better stacks for async errors.
142 | //
143 | // TODO(nevir): Can we expand support to other browsers?
144 | if (navigator.userAgent.match(/chrome/i)) {
145 | window.onerror = null;
146 | window.addEventListener('error', function(event) {
147 | if (!event.error)
148 | return;
149 | if (event.error.ignore)
150 | return;
151 | runner.uncaught(event.error);
152 | });
153 | }
154 | }
155 | /**
156 | * In Chrome57 custom elements in the document might not get upgraded when
157 | * there is a high GC
158 | * https://bugs.chromium.org/p/chromium/issues/detail?id=701601 We clone and
159 | * replace the ones that weren't upgraded.
160 | */
161 | function _fixCustomElements() {
162 | // Bail out if it is not Chrome 57.
163 | const raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
164 | const isM57 = raw && raw[2] === '57';
165 | if (!isM57)
166 | return;
167 |
168 | const elements = document.body.querySelectorAll('*:not(script):not(style)');
169 | const constructors = {};
170 | for (let i = 0; i < elements.length; i++) {
171 | const el = elements[i];
172 | // This child has already been cloned and replaced by its parent, skip it!
173 | if (!el.isConnected)
174 | continue;
175 |
176 | const tag = el.localName;
177 | // Not a custom element!
178 | if (tag.indexOf('-') === -1)
179 | continue;
180 |
181 | // Memoize correct constructors.
182 | constructors[tag] =
183 | constructors[tag] || document.createElement(tag).constructor;
184 | // This one was correctly upgraded.
185 | if (el instanceof constructors[tag])
186 | continue;
187 |
188 | util.debug('_fixCustomElements: found non-upgraded custom element ' + el);
189 | const clone = document.importNode(el, true);
190 | el.parentNode.replaceChild(clone, el);
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/browser/environment/helpers.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt The complete set of authors may be found
6 | * at http://polymer.github.io/AUTHORS.txt The complete set of contributors may
7 | * be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by
8 | * Google as part of the polymer project is also subject to an additional IP
9 | * rights grant found at http://polymer.github.io/PATENTS.txt
10 | */
11 |
12 | export {};
13 |
14 | // Make sure that we use native timers, in case they're being stubbed out.
15 | const nativeSetInterval = window.setInterval;
16 | const nativeSetTimeout = window.setTimeout;
17 | const nativeRequestAnimationFrame = window.requestAnimationFrame;
18 |
19 |
20 |
21 | /**
22 | * Runs `stepFn`, catching any error and passing it to `callback` (Node-style).
23 | * Otherwise, calls `callback` with no arguments on success.
24 | *
25 | * @param {function()} callback
26 | * @param {function()} stepFn
27 | */
28 | function safeStep(callback: (error?: any) => void, stepFn: () => void) {
29 | let err;
30 | try {
31 | stepFn();
32 | } catch (error) {
33 | err = error;
34 | }
35 | callback(err);
36 | }
37 |
38 | /**
39 | * Runs your test at declaration time (before Mocha has begun tests). Handy for
40 | * when you need to test document initialization.
41 | *
42 | * Be aware that any errors thrown asynchronously cannot be tied to your test.
43 | * You may want to catch them and pass them to the done event, instead. See
44 | * `safeStep`.
45 | *
46 | * @param {string} name The name of the test.
47 | * @param {function(?function())} testFn The test function. If an argument is
48 | * accepted, the test will be treated as async, just like Mocha tests.
49 | */
50 | function testImmediate(name: string, testFn: Function) {
51 | if (testFn.length > 0) {
52 | return testImmediateAsync(name, testFn);
53 | }
54 |
55 | let err: any;
56 | try {
57 | testFn();
58 | } catch (error) {
59 | err = error;
60 | }
61 | test(name, function(done) {
62 | done(err);
63 | });
64 | }
65 |
66 | /**
67 | * An async-only variant of `testImmediate`.
68 | *
69 | * @param {string} name
70 | * @param {function(?function())} testFn
71 | */
72 | function testImmediateAsync(name: string, testFn: Function) {
73 | let testComplete = false;
74 | let err: any;
75 |
76 | test(name, function(done) {
77 | const intervalId = nativeSetInterval(function() {
78 | if (!testComplete)
79 | return;
80 | clearInterval(intervalId);
81 | done(err);
82 | }, 10);
83 | });
84 |
85 | try {
86 | testFn(function(error: any) {
87 | if (error)
88 | err = error;
89 | testComplete = true;
90 | });
91 | } catch (error) {
92 | err = error;
93 | testComplete = true;
94 | }
95 | }
96 |
97 | /**
98 | * Triggers a flush of any pending events, observations, etc and calls you back
99 | * after they have been processed.
100 | *
101 | * @param {function()} callback
102 | */
103 | function flush(callback: () => void) {
104 | // Ideally, this function would be a call to Polymer.dom.flush, but that
105 | // doesn't support a callback yet
106 | // (https://github.com/Polymer/polymer-dev/issues/851),
107 | // ...and there's cross-browser flakiness to deal with.
108 |
109 | // Make sure that we're invoking the callback with no arguments so that the
110 | // caller can pass Mocha callbacks, etc.
111 | let done = function done() {
112 | callback();
113 | };
114 |
115 | // Because endOfMicrotask is flaky for IE, we perform microtask checkpoints
116 | // ourselves (https://github.com/Polymer/polymer-dev/issues/114):
117 | const isIE = navigator.appName === 'Microsoft Internet Explorer';
118 | if (isIE && window.Platform && window.Platform.performMicrotaskCheckpoint) {
119 | const reallyDone = done;
120 | done = function doneIE() {
121 | Platform.performMicrotaskCheckpoint();
122 | nativeSetTimeout(reallyDone, 0);
123 | };
124 | }
125 |
126 | // Everyone else gets a regular flush.
127 | let scope;
128 | if (window.Polymer && window.Polymer.dom && window.Polymer.dom.flush) {
129 | scope = window.Polymer.dom;
130 | } else if (window.Polymer && window.Polymer.flush) {
131 | scope = window.Polymer;
132 | } else if (window.WebComponents && window.WebComponents.flush) {
133 | scope = window.WebComponents;
134 | }
135 | if (scope) {
136 | scope.flush();
137 | }
138 |
139 | // Ensure that we are creating a new _task_ to allow all active microtasks to
140 | // finish (the code you're testing may be using endOfMicrotask, too).
141 | nativeSetTimeout(done, 0);
142 | }
143 |
144 | /**
145 | * Advances a single animation frame.
146 | *
147 | * Calls `flush`, `requestAnimationFrame`, `flush`, and `callback` sequentially
148 | * @param {function()} callback
149 | */
150 | function animationFrameFlush(callback: () => void) {
151 | flush(function() {
152 | nativeRequestAnimationFrame(function() {
153 | flush(callback);
154 | });
155 | });
156 | }
157 |
158 | /**
159 | * DEPRECATED: Use `flush`.
160 | * @param {function} callback
161 | */
162 | function asyncPlatformFlush(callback: () => void) {
163 | console.warn(
164 | 'asyncPlatformFlush is deprecated in favor of the more terse flush()');
165 | return window.flush(callback);
166 | }
167 |
168 | export interface MutationEl {
169 | onMutation(mutationEl: this, cb: () => void): void;
170 | }
171 |
172 | /**
173 | *
174 | */
175 | function waitFor(
176 | fn: () => void, next: () => void, intervalOrMutationEl: number|MutationEl,
177 | timeout: number, timeoutTime: number) {
178 | timeoutTime = timeoutTime || Date.now() + (timeout || 1000);
179 | intervalOrMutationEl = intervalOrMutationEl || 32;
180 | try {
181 | fn();
182 | } catch (e) {
183 | if (Date.now() > timeoutTime) {
184 | throw e;
185 | } else {
186 | if (typeof intervalOrMutationEl !== 'number') {
187 | intervalOrMutationEl.onMutation(intervalOrMutationEl, function() {
188 | waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
189 | });
190 | } else {
191 | nativeSetTimeout(function() {
192 | waitFor(fn, next, intervalOrMutationEl, timeout, timeoutTime);
193 | }, intervalOrMutationEl);
194 | }
195 | return;
196 | }
197 | }
198 | next();
199 | }
200 |
201 | declare global {
202 | interface Window {
203 | safeStep: typeof safeStep;
204 | testImmediate: typeof testImmediate;
205 | testImmediateAsync: typeof testImmediateAsync;
206 | flush: typeof flush;
207 | animationFrameFlush: typeof animationFrameFlush;
208 | asyncPlatformFlush: typeof asyncPlatformFlush;
209 | waitFor: typeof waitFor;
210 | }
211 | }
212 |
213 | window.safeStep = safeStep;
214 | window.testImmediate = testImmediate;
215 | window.testImmediateAsync = testImmediateAsync;
216 | window.flush = flush;
217 | window.animationFrameFlush = animationFrameFlush;
218 | window.asyncPlatformFlush = asyncPlatformFlush;
219 | window.waitFor = waitFor;
220 |
--------------------------------------------------------------------------------
/test/fixtures/integration/multiple-replace/test/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
233 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
7 | * Code distributed by Google as part of the polymer project is also
8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
9 | */
10 | 'use strict';
11 |
12 | const concat = require('gulp-concat');
13 | const depcheck = require('depcheck');
14 | const fs = require('fs');
15 | const glob = require('glob');
16 | const gulp = require('gulp');
17 | const bower = require('gulp-bower');
18 | const mocha = require('gulp-spawn-mocha');
19 | const tslint = require('gulp-tslint');
20 | const ts = require('gulp-typescript');
21 | const lazypipe = require('lazypipe');
22 | const path = require('path');
23 | const rollup = require('rollup');
24 | const runSequence = require('run-sequence');
25 | const typescript = require('typescript');
26 |
27 |
28 | // const commonTools = require('tools-common/gulpfile');
29 | const commonTools = {
30 | depcheck: commonDepCheck
31 | };
32 |
33 | gulp.task('lint', ['tslint', 'depcheck']);
34 |
35 | // Meta tasks
36 |
37 | gulp.task('default', ['test']);
38 |
39 | function removeFile(path) {
40 | try {
41 | fs.unlinkSync(path);
42 | return;
43 | } catch (e) {
44 | try {
45 | fs.statSync(path);
46 | } catch (e) {
47 | return;
48 | }
49 | throw new Error('Unable to remove file: ' + path);
50 | }
51 | }
52 |
53 | gulp.task('clean', (done) => {
54 | removeFile('browser.js');
55 | removeFile('browser.js.map');
56 | const patterns = ['runner/*.js', 'browser/**/*.js', 'browser/**/*.js.map'];
57 | for (const pattern of patterns) {
58 | glob(pattern, (err, files) => {
59 | if (err) {
60 | return done(err);
61 | }
62 | try {
63 | for (const file of files) {
64 | removeFile(file);
65 | }
66 | } catch (e) {
67 | return done(e);
68 | }
69 | });
70 | }
71 | done();
72 | });
73 |
74 | gulp.task('test', function (done) {
75 | runSequence(
76 | 'build:typescript-server',
77 | 'lint',
78 | 'test:unit',
79 | 'test:integration',
80 | done);
81 | });
82 |
83 | gulp.task('build-all', (done) => {
84 | runSequence('clean', 'lint', 'build', done);
85 | });
86 |
87 | gulp.task('build',
88 | ['build:typescript-server', 'build:browser', 'build:wct-browser-legacy']);
89 |
90 | const tsProject = ts.createProject('tsconfig.json', { typescript });
91 | gulp.task('build:typescript-server', function () {
92 | // Ignore typescript errors, because gulp-typescript, like most things
93 | // gulp, can't be trusted.
94 | return tsProject.src().pipe(tsProject(ts.reporter.nullReporter())).js.pipe(gulp.dest('./'));
95 | });
96 |
97 | const browserTsProject = ts.createProject('browser/tsconfig.json', {
98 | typescript
99 | });
100 | gulp.task('build:typescript-browser', function () {
101 | return browserTsProject.src().pipe(
102 | browserTsProject(ts.reporter.nullReporter())).js.pipe(gulp.dest('./browser/'));
103 | });
104 |
105 | // Specific tasks
106 |
107 | gulp.task('build:browser', ['build:typescript-browser'], function (done) {
108 | rollup.rollup({
109 | entry: 'browser/index.js',
110 | }).then(function (bundle) {
111 | bundle.write({
112 | indent: false,
113 | format: 'iife',
114 | banner: fs.readFileSync('browser-js-header.txt', 'utf-8'),
115 | intro: 'window.__wctUseNpm = false;',
116 | dest: 'browser.js',
117 | sourceMap: true,
118 | sourceMapFile: path.resolve('browser.js.map')
119 | }).then(function () {
120 | done();
121 | });
122 | }).catch(done);
123 | });
124 |
125 | gulp.task('build:wct-browser-legacy:a11ySuite', function () {
126 | return gulp.src(['data/a11ySuite-npm-header.txt', 'data/a11ySuite.js'])
127 | .pipe(concat('a11ySuite.js'))
128 | .pipe(gulp.dest('wct-browser-legacy/'));
129 | });
130 |
131 | gulp.task('build:wct-browser-legacy:browser', ['build:typescript-browser'], function (done) {
132 | rollup.rollup({
133 | entry: 'browser/index.js',
134 | }).then(function (bundle) {
135 | bundle.write({
136 | indent: false,
137 | format: 'iife',
138 | banner: fs.readFileSync('browser-js-header.txt', 'utf-8'),
139 | intro: 'window.__wctUseNpm = true;',
140 | dest: 'wct-browser-legacy/browser.js',
141 | sourceMap: true,
142 | sourceMapFile: path.resolve('browser.js.map')
143 | }).then(function () {
144 | done();
145 | });
146 | }).catch(done);
147 | });
148 |
149 | gulp.task('build:wct-browser-legacy', [
150 | 'build:wct-browser-legacy:a11ySuite',
151 | 'build:wct-browser-legacy:browser',
152 | ]);
153 |
154 |
155 | gulp.task('test:unit', function () {
156 | return gulp.src('test/unit/*.js', { read: false })
157 | .pipe(mocha({ reporter: 'spec' }));
158 | });
159 |
160 | gulp.task('bower', function () {
161 | return bower();
162 | });
163 |
164 | gulp.task('test:integration', ['bower'], function () {
165 | return gulp.src('test/integration/*.js', { read: false })
166 | .pipe(mocha({ reporter: 'spec' }));
167 | });
168 |
169 | gulp.task('tslint', () =>
170 | gulp.src([
171 | 'runner/**/*.ts', '!runner/**/*.d.ts',
172 | 'test/**/*.ts', '!test/**/*.d.ts',
173 | 'custom_typings/*.d.ts', 'browser/**/*.ts', '!browser/**/*.ts'
174 | ])
175 | .pipe(tslint())
176 | .pipe(tslint.report({ formatter: 'verbose' })));
177 |
178 | // Flows
179 |
180 | commonTools.depcheck({
181 | stickyDeps: new Set([
182 | // Used in browser.js
183 | 'accessibility-developer-tools',
184 | 'mocha',
185 | 'test-fixture',
186 | '@polymer/sinonjs',
187 | '@polymer/test-fixture',
188 | '@webcomponents/webcomponentsjs',
189 | 'async',
190 |
191 | // Only included to satisfy peer dependency and suppress error on install
192 | 'sinon',
193 |
194 | // Used in the wct binary
195 | 'resolve'
196 | ])
197 | });
198 |
199 | function commonDepCheck(options) {
200 | const defaultOptions = { stickyDeps: new Set() };
201 | options = Object.assign({}, defaultOptions, options);
202 |
203 | gulp.task('depcheck', () => {
204 | return new Promise((resolve, reject) => {
205 | depcheck(
206 | __dirname, { ignoreDirs: [], ignoreMatches: ['@types/*'] }, resolve);
207 | }).then((result) => {
208 | const invalidFiles = Object.keys(result.invalidFiles) || [];
209 | const invalidJsFiles = invalidFiles.filter((f) => f.endsWith('.js'));
210 |
211 | if (invalidJsFiles.length > 0) {
212 | console.log('Invalid files:', result.invalidFiles);
213 | throw new Error('Invalid files');
214 | }
215 |
216 | const unused = new Set(result.dependencies);
217 | for (const falseUnused of options.stickyDeps) {
218 | unused.delete(falseUnused);
219 | }
220 | if (unused.size > 0) {
221 | console.log('Unused dependencies:', unused);
222 | throw new Error('Unused dependencies');
223 | }
224 | });
225 | });
226 | }
227 |
228 | gulp.task('prepublish', function (done) {
229 | // We can't run the integration tests here because on travis we may not
230 | // be running with an x instance when we do `npm install`. We can change
231 | // this to just `test` from `test:unit` once all supported npm versions
232 | // no longer run `prepublish` on install.
233 | runSequence('build-all', 'test:unit', done);
234 | });
235 |
--------------------------------------------------------------------------------
/runner/context.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @license
3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
4 | * This code may only be used under the BSD style license found at
5 | * http://polymer.github.io/LICENSE.txt
6 | * The complete set of authors may be found at
7 | * http://polymer.github.io/AUTHORS.txt
8 | * The complete set of contributors may be found at
9 | * http://polymer.github.io/CONTRIBUTORS.txt
10 | * Code distributed by Google as part of the polymer project is also
11 | * subject to an additional IP rights grant found at
12 | * http://polymer.github.io/PATENTS.txt
13 | */
14 |
15 | import * as events from 'events';
16 | import * as express from 'express';
17 | import * as _ from 'lodash';
18 | import {ExpressAppMapper, ServerOptions} from 'polyserve/lib/start_server';
19 | import * as socketIO from 'socket.io';
20 | import * as http from 'spdy';
21 | import * as util from 'util';
22 |
23 | import {BrowserRunner} from './browserrunner';
24 | import * as config from './config';
25 | import {Plugin} from './plugin';
26 |
27 | const JSON_MATCHER = 'wct.conf.json';
28 | const CONFIG_MATCHER = 'wct.conf.*';
29 |
30 | export type Handler =
31 | ((...args: any[]) => Promise)|((done: (err?: any) => void) => void)|
32 | ((arg1: any, done: (err?: any) => void) => void)|
33 | ((arg1: any, arg2: any, done: (err?: any) => void) => void)|
34 | ((arg1: any, arg2: any, arg3: any, done: (err?: any) => void) => void);
35 |
36 | /**
37 | * Exposes the current state of a WCT run, and emits events/hooks for anyone
38 | * downstream to listen to.
39 | *
40 | * TODO(rictic): break back-compat with plugins by moving hooks entirely away
41 | * from callbacks to promises. Easiest way to do this would be to rename
42 | * the hook-related methods on this object, so that downstream callers would
43 | * break in obvious ways.
44 | *
45 | * @param {Object} options Any initially specified options.
46 | */
47 | export class Context extends events.EventEmitter {
48 | options: config.Config;
49 | private _hookHandlers: {[key: string]: Handler[]} = {};
50 | _socketIOServers: SocketIO.Server[];
51 | _httpServers: http.Server[];
52 | _testRunners: BrowserRunner[];
53 |
54 | constructor(options?: config.Config) {
55 | super();
56 | options = options || {};
57 |
58 | let matcher: string;
59 | if (options.configFile) {
60 | matcher = options.configFile;
61 | } else if (options.enforceJsonConf) {
62 | matcher = JSON_MATCHER;
63 | } else {
64 | matcher = CONFIG_MATCHER;
65 | }
66 |
67 | /**
68 | * The configuration for the current WCT run.
69 | *
70 | * We guarantee that this object is never replaced (e.g. you are free to
71 | * hold a reference to it, and make changes to it).
72 | */
73 | this.options = config.merge(
74 | config.defaults(), config.fromDisk(matcher, options.root), options);
75 | }
76 |
77 | // Hooks
78 | //
79 | // In addition to emitting events, a context also exposes "hooks" that
80 | // interested parties can use to inject behavior.
81 |
82 | /**
83 | * Registers a handler for a particular hook. Hooks are typically configured
84 | * to run _before_ a particular behavior.
85 | */
86 | hook(name: string, handler: Handler) {
87 | this._hookHandlers[name] = this._hookHandlers[name] || [];
88 | this._hookHandlers[name].unshift(handler);
89 | }
90 |
91 | /**
92 | * Registers a handler that will run after any handlers registered so far.
93 | *
94 | * @param {string} name
95 | * @param {function(!Object, function(*))} handler
96 | */
97 | hookLate(name: string, handler: Handler) {
98 | this._hookHandlers[name] = this._hookHandlers[name] || [];
99 | this._hookHandlers[name].push(handler);
100 | }
101 |
102 | /**
103 | * Once all registered handlers have run for the hook, your callback will be
104 | * triggered. If any of the handlers indicates an error state, any subsequent
105 | * handlers will be canceled, and the error will be passed to the callback for
106 | * the hook.
107 | *
108 | * Any additional arguments passed between `name` and `done` will be passed to
109 | * hooks (before the callback).
110 | *
111 | * @param {string} name
112 | * @param {function(*)} done
113 | * @return {!Context}
114 | */
115 | emitHook(
116 | name: 'define:webserver', app: express.Express,
117 | // The `mapper` param is a function the client of the hook uses to
118 | // substitute a new app for the one given. This enables, for example,
119 | // mounting the polyserve app on a custom app to handle requests or mount
120 | // middleware that needs to sit in front of polyserve's own handlers.
121 | mapper: (app: Express.Application) => void, options: ServerOptions,
122 | done?: (err?: any) => void): Promise;
123 | emitHook(
124 | name: 'prepare:webserver', app: express.Express,
125 | done?: (err?: any) => void): Promise;
126 | emitHook(name: 'configure', done?: (err?: any) => void): Promise;
127 | emitHook(name: 'prepare', done?: (err?: any) => void): Promise;
128 | emitHook(name: 'cleanup', done?: (err?: any) => void): Promise;
129 | emitHook(name: string, done?: (err?: any) => void): Promise;
130 | emitHook(name: string, ...args: any[]): Promise;
131 |
132 | async emitHook(name: string, ...args: any[]): Promise {
133 | this.emit('log:debug', 'hook:', name);
134 |
135 | const hooks = (this._hookHandlers[name] || []);
136 | type BoundHook = (cb: (err: any) => void) => (void|Promise);
137 | let boundHooks: BoundHook[];
138 | let done: (err?: any) => void = (_err: any) => {};
139 | let argsEnd = args.length - 1;
140 | if (args[argsEnd] instanceof Function) {
141 | done = args[argsEnd];
142 | argsEnd = argsEnd--;
143 | }
144 | const hookArgs = args.slice(0, argsEnd + 1);
145 | boundHooks =
146 | hooks.map((hook) => hook.bind.apply(hook, [null].concat(hookArgs)));
147 | if (!boundHooks) {
148 | boundHooks = hooks;
149 | }
150 |
151 | // A hook may return a promise or it may call a callback. We want to
152 | // treat hooks as though they always return promises, so this converts.
153 | const hookToPromise = (hook: BoundHook) => {
154 | return new Promise((resolve, reject) => {
155 | const maybePromise = hook((err) => {
156 | if (err) {
157 | reject(err);
158 | } else {
159 | resolve();
160 | }
161 | });
162 | if (maybePromise) {
163 | maybePromise.then(resolve, reject);
164 | }
165 | });
166 | };
167 |
168 | // We execute the handlers _sequentially_. This may be slower, but it gives
169 | // us a lighter cognitive load and more obvious logs.
170 | try {
171 | for (const hook of boundHooks) {
172 | await hookToPromise(hook);
173 | }
174 | } catch (err) {
175 | // TODO(rictic): stop silently swallowing the error here and just below.
176 | // Looks like we'll need to track down some error being thrown from
177 | // deep inside the express router.
178 | try {
179 | done(err);
180 | } catch (_) {
181 | }
182 | throw err;
183 | }
184 | try {
185 | done();
186 | } catch (_) {
187 | }
188 | }
189 |
190 | /**
191 | * @param {function(*, Array)} done Asynchronously loads the plugins
192 | * requested by `options.plugins`.
193 | */
194 | async plugins(): Promise {
195 | const plugins: Plugin[] = [];
196 | for (const name of this.enabledPlugins()) {
197 | plugins.push(await Plugin.get(name));
198 | }
199 | return plugins;
200 | }
201 |
202 | /**
203 | * @return {!Array} The names of enabled plugins.
204 | */
205 | enabledPlugins(): string[] {
206 | // Plugins with falsy configuration or disabled: true are _not_ loaded.
207 | const pairs = _.reject(
208 | (_).pairs(this.options.plugins),
209 | (p: [string, {disabled: boolean}]) => !p[1] || p[1].disabled);
210 | return _.map(pairs, (p) => p[0]);
211 | }
212 |
213 | /**
214 | * @param {string} name
215 | * @return {!Object}
216 | */
217 | pluginOptions(name: string) {
218 | return this.options.plugins[Plugin.shortName(name)];
219 | }
220 |
221 | static Context = Context;
222 | }
223 |
224 | module.exports = Context;
225 |
--------------------------------------------------------------------------------