Skip to content

Commit

Permalink
Merge pull request #727 from qawolf/feat-support-input-fill
Browse files Browse the repository at this point in the history
feat: use 'fill' for inputs
  • Loading branch information
aldeed committed Jul 11, 2020
2 parents 7775e5d + 2942717 commit 3afe743
Show file tree
Hide file tree
Showing 101 changed files with 56,762 additions and 787 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,21 @@ npx qawolf create url [name]

💪 Convert your actions into [Playwright](https://github.com/microsoft/playwright) code:

| Action | Status | Example |
| ------------------------------------------------------------ | :----: | ---------------------------------------------------------- |
| Click || `page.click(selectors['0_submit'])` |
| Type || `page.type(selectors['0_username'], 'username')` |
| Scroll || `qawolf.scroll(page, 'html', { x: 0, y: 200 })` |
| Select || `page.selectOption(selectors['0_ice_cream'], 'chocolate')` |
| Replace text || `page.fill(selectors['0_username'], 'username')` |
| Paste || `page.type(selectors['password'], 'pasted')` |
| Use a test attribute || `page.click("[data-qa='submit']")` |
| Use a test attribute on an ancestor || `page.click("[data-qa='radio'] [value='cat']")` |
| Use multiple pages/tabs || `qawolf.waitForPage(page.context(), 1)` |
| [Iframes](https://github.com/qawolf/qawolf/issues/279) | 🗺️ | Coming soon |
| [Drag and drop](https://github.com/qawolf/qawolf/issues/315) | 🗺️ | Coming soon |
| [File upload](https://github.com/qawolf/qawolf/issues/331) | 🗺️ | Coming soon |
| [Back button](https://github.com/qawolf/qawolf/issues/438) | 🗺️ | Coming soon |
| Action | Status | Example |
| ------------------------------------------------------------ | :----: | ----------------------------------------------- |
| Click || `page.click('#login')` |
| Type || `page.fill('.username', 'spirit@qawolf.com')` |
| Scroll || `qawolf.scroll(page, 'html', { x: 0, y: 200 })` |
| Select || `page.selectOption('.ice_cream', 'chocolate')` |
| Replace text || `page.fill('.username', 'username')` |
| Paste || `page.type('password', 'pasted')` |
| Use a test attribute || `page.click("[data-qa='submit']")` |
| Use a test attribute on an ancestor || `page.click("[data-qa='radio'] [value='cat']")` |
| Use multiple pages/tabs || `qawolf.waitForPage(page.context(), 1)` |
| [Iframes](https://github.com/qawolf/qawolf/issues/279) | 🗺️ | Coming soon |
| [Drag and drop](https://github.com/qawolf/qawolf/issues/315) | 🗺️ | Coming soon |
| [File upload](https://github.com/qawolf/qawolf/issues/331) | 🗺️ | Coming soon |
| [Back button](https://github.com/qawolf/qawolf/issues/438) | 🗺️ | Coming soon |

As your test is created:

Expand Down
39 changes: 36 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
},
"devDependencies": {
"@ffmpeg-installer/ffmpeg": "^1.0.20",
"@qawolf/sandbox": "0.1.12",
"@qawolf/sandbox": "0.1.13",
"@types/debug": "^4.1.5",
"@types/fs-extra": "^9.0.1",
"@types/glob": "^7.1.3",
Expand All @@ -89,6 +89,7 @@
"rimraf": "^3.0.2",
"ts-jest": "^26.1.1",
"ts-loader": "^7.0.5",
"ts-node": "^8.10.2",
"typescript": "^3.9.6",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12",
Expand Down
2 changes: 1 addition & 1 deletion packages/sandbox/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/sandbox/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@qawolf/sandbox",
"version": "0.1.12",
"version": "0.1.13",
"files": [
"bin",
"build"
Expand Down
5 changes: 5 additions & 0 deletions packages/sandbox/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import LogIn from './pages/LogIn';
import NestedDataAttributes from './pages/NestedDataAttributes';
import RadioInputs from './pages/RadioInputs';
import Selects from './pages/Selects';
import SpecialInputs from './pages/SpecialInputs';
import TextInputs from './pages/TextInputs';
import TimePickers from './pages/TimePickers';
// CSS
Expand Down Expand Up @@ -58,6 +59,9 @@ function Navigation() {
<li>
<Link to="/selects">Selects</Link>
</li>
<li>
<Link to="/special-inputs">Special inputs</Link>
</li>
<li>
<Link to="/text-inputs">Text inputs</Link>
</li>
Expand Down Expand Up @@ -87,6 +91,7 @@ function App() {
/>
<Route component={RadioInputs} path="/radio-inputs" />
<Route component={Selects} path="/selects" />
<Route component={SpecialInputs} path="/special-inputs" />
<Route component={TextInputs} path="/text-inputs" />
<Route component={TimePickers} path="/time-pickers" />
<Redirect to="/" />
Expand Down
5 changes: 5 additions & 0 deletions packages/sandbox/src/pages/DatePickers/HtmlDatePickers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ function HtmlDatePickers() {
return (
<div className="container">
<h3>Native HTML</h3>
<h4>Date</h4>
<input data-qa="html-date-picker" type="date" />
<h4>Week</h4>
<input data-qa="html-week-picker" type="week" />
<h4>Month</h4>
<input data-qa="html-month-picker" type="month" />
</div>
);
}
Expand Down
15 changes: 15 additions & 0 deletions packages/sandbox/src/pages/SpecialInputs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";

function SpecialInputs() {
return (
<div className="container">
<h3>Native HTML</h3>
<h4>Color</h4>
<input data-qa="html-color-picker" type="color" />
<h4>Range</h4>
<input data-qa="html-range-picker" type="range" min="0" max="10" />
</div>
);
}

export default SpecialInputs;
12 changes: 12 additions & 0 deletions packages/sandbox/src/pages/TextInputs/HtmlTextInputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ function HtmlTextInputs() {
/>
<br />
<br />
<input data-qa="html-search-input" placeholder="Search input" type="search" />
<br />
<br />
<input data-qa="html-email-input" placeholder="Email input" type="email" />
<br />
<br />
<input data-qa="html-url-input" placeholder="URL input" type="url" />
<br />
<br />
<input data-qa="html-tel-input" placeholder="Telephone input" type="tel" />
<br />
<br />
<input
data-qa="html-password-input"
placeholder="Password input"
Expand Down
3 changes: 3 additions & 0 deletions packages/sandbox/src/pages/TimePickers/HtmlTimePickers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ function HtmlTimePickers() {
return (
<div className="container">
<h3>Native HTML</h3>
<h4>Time</h4>
<input data-qa="html-time-picker" type="time" />
<h4>Local date and time</h4>
<input data-qa="html-datetime-local-picker" type="datetime-local" />
</div>
);
}
Expand Down
8 changes: 3 additions & 5 deletions src/build-workflow/buildClickSteps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Debug from 'debug';
import { ElementEvent, Step } from '../types';
import { isInputTarget } from './target';

const debug = Debug('qawolf:buildClickSteps');

Expand All @@ -17,7 +18,7 @@ const filterClickEvents = (events: ElementEvent[]): ElementEvent[] => {
const previousEvent = events[i - 1];
if (
previousEvent &&
['keydown', 'keyup'].includes(previousEvent.name) &&
['change', 'keydown', 'keyup'].includes(previousEvent.name) &&
event.time - previousEvent.time < 50
) {
// skip system-initiated clicks triggered by a key press
Expand Down Expand Up @@ -75,10 +76,7 @@ export const buildClickSteps = (events: ElementEvent[]): Step[] => {
groupedClickEvents.forEach((events) => {
let event = events[0] as ElementEvent;

const inputEvent = events.find((event) => {
const name = event.target.name || '';
return name.toLowerCase() === 'input';
});
const inputEvent = events.find((event) => isInputTarget(event.target));
if (inputEvent) {
// if an event in the group is on an input, assume the click propagated
// to an element like a checkbox or radio, which is most accurate target
Expand Down
67 changes: 67 additions & 0 deletions src/build-workflow/buildFillSteps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Debug from 'debug';
import {
ElementEvent,
InputEvent,
Step,
} from '../types';
import { isChangeEvent, isInputEvent } from './event';
import { isContentEditableTarget, isInputTarget, isTextareaTarget } from './target';

const debug = Debug('qawolf:buildFillSteps');

const shouldFill = (event: ElementEvent): boolean => {
const { target } = event;
return (isInputEvent(event) || isChangeEvent(event)) &&
(isInputTarget(target) || isTextareaTarget(target) || isContentEditableTarget(target)) &&
// Some inputs emit "change" with a value but really can't or shouldn't be
// "filled in" with that value. Checkbox and radio should work without filling
// because there will be click events. File isn't supported.
![
'checkbox',
'radio',
'file'
].includes(target.attrs && target.attrs.type);
}

/**
* @summary Given a list of captured browser page events, returns a list of input fill
* steps that should be included when playing back the flow for testing purposes.
*/
export const buildFillSteps = (events: ElementEvent[]): Step[] => {
debug('building fill steps');

const fillSteps = [];
let lastInputEvent: ElementEvent;
for (let index = 0; index < events.length; index++) {
const event = events[index];
if (isInputEvent(event)) {
lastInputEvent = event;
}
if (lastInputEvent && shouldFill(event)) {
// We use change events to determine when an input fill needs to happen
// but they always happen AFTER the Tab, Enter, click, etc. that caused
// the change to be committed. So instead of attaching the change event
// to the fill, we attach the most recent input event, which happened
// before the keydown or click.
const { value } = event as InputEvent;
// Also most inputs that have masks tend to fire "change" event multiple
// times while you are typing in the mask. We only want the final fill value,
// so if this fill is identical to the previous fill other than the value,
// we'll overwrite the previous.
const previousFill = fillSteps[fillSteps.length - 1];
if (previousFill && previousFill.event.selector === lastInputEvent.selector) {
previousFill.event = lastInputEvent;
previousFill.value = value;
} else {
fillSteps.push({
action: 'fill',
event: lastInputEvent,
index: fillSteps.length,
value,
});
}
}
}

return fillSteps;
};

0 comments on commit 3afe743

Please sign in to comment.