Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: mouse and keyboard indicators #3526

Closed

Conversation

JoelEinbinder
Copy link
Contributor

Adds an overlay that shows the current mouse position and typed text. I have a test but its waiting on some fixture refactorings I'm working on.

The overlays are added into the dom. While their guts are hidden with a shadow root, they still can be detected by JavaScript on the page. In practice I don't expect this to be an issue.

I'd like future patches to animate the mouse cursor and show "to-be-typed" text for a smoother experience.

Copy link
Collaborator

@aslushnikov aslushnikov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

Let's make sure everyone is happy with API names (I am)

Also, this'll close #1374

src/input.ts Outdated Show resolved Hide resolved
docs/api.md Outdated
@@ -4135,6 +4135,8 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
- `options` <[Object]>
- `wsEndpoint` <[string]> A browser websocket endpoint to connect to. **required**
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. Defaults to 0.
- `showMouseIndicator` <[boolean]> Show Playwright's mouse cursor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider doing this under PWDEBUG instead? How do you see these options being used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's land this behind env instead.

@JoelEinbinder JoelEinbinder force-pushed the mouse_keyboard_indicator branch 2 times, most recently from cb88ddb to b4815fa Compare August 28, 2020 09:35
@JoelEinbinder
Copy link
Contributor Author

@dgozman and I talked offline about this.

  • PWDEBUG doesn't replace javascript apis.
  • It would be nice to have a simple option debug: true which does headless: false, slowMo: 250, showKeyboardIndicator: true, etc.
  • Does the single debug option replace the individual options? No. The individual options make our documentation story much nicer, and also lets us add new features to debug: true without it being a breaking change.
  • We proposed having debug: true as well as debug: {mouse: true, sourcemaps: true}. This avoids cluttering the launch api with many options.

By myself I had a change of heart about debug: {mouse: true}. It gets tricky because we want debug to set headless: false, but headless is not always a debugging feature. It also is already in the launch api, so we would have to mirror it and document what happens when you specify both debug: {headless: true} and headless: false.

Instead I was inspired by typescript's "strict": true, which has related options "strictBindCallApply", "strictNullChecks", etc. I've renamed showMouseIndicator into debugMouse and debugKeyboard. Then when we add debug, it will be clear that it turns on all options prefixed with debug. And when im launching my browser, I can safely ignore all options that being with debug.

function createKeyboardIndicator() {
const element = document.createElement('playwright-keyboard-indicator');
const shadow = element.attachShadow({mode: 'closed'});
shadow.innerHTML = `
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our injected script already has html library for debug overlays, let's align those implementations.

@@ -50,14 +51,98 @@ export class Keyboard {
this._page = page;
}

async _updateKeyboardIndicator(description?: KeyDescription) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get some code modularity going. This is a debugging code, it needs to be strictly separate from the code functionality. See what Dmitry did for tracing - make sure your auxiliary feature is using apis / dependency injection where necessary.

Copy link
Member

@pavelfeldman pavelfeldman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for modularity, we can't pollute core.

@JoelEinbinder JoelEinbinder force-pushed the mouse_keyboard_indicator branch 2 times, most recently from e16de46 to c937ef0 Compare November 10, 2020 08:00
@JoelEinbinder
Copy link
Contributor Author

Requesting changes for modularity, we can't pollute core.

This has been updated. ptal.

@pavelfeldman
Copy link
Member

Actually, still needs work. It is disconnected from the existing debugging features and cli, we should get some consistency going there.

@Aaron-Pool
Copy link

Just chiming in to say that I would love to see this feature get integrated 👍

@JoelEinbinder
Copy link
Contributor Author

Merged all of the options into showUserInput. Refactored to use a context listener instead of wrapping the touch events. @dgozman ptal thanks!

@@ -157,6 +157,11 @@ headless`] option will be set `false`.

Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.

### option: BrowserType.launch.showUiserInput
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo in the option name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we lost linting for these

docs/src/api/class-browsertype.md Outdated Show resolved Hide resolved
src/server/browserContext.ts Show resolved Hide resolved
src/server/browserContext.ts Show resolved Hide resolved
}

export type InputEvent = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like instrumentation has grown large enough to justify a separate file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I made a contextListeners file.

test/indicators.spec.ts Outdated Show resolved Hide resolved
src/debug/showUserInput.ts Outdated Show resolved Hide resolved
shadow.appendChild(div);
}
if (timeout)
clearInterval(timeout);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses setTimeout + clearInterval. Let's decide whether it's a timeout or an interval 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timeout = setTimeout(() => {
div.remove();
timeout = null;
}, 200);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Played with this, 200ms is too short for me. WDYT about reusing the slowMo value is present? Same for tap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. (slowMo || 150) + 50

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For tap I think always 500ms is fine. We treat tap as an atomic action and dont slowMo inbetween touchstart and touchend.

text = '';
} else if (!modifiers.size && key.trim().length === 1) {
const currentText = (textForPage.get(page) || '') + key;
textForPage.set(page, currentText);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should reset this somewhere, otherwise multiple texts will coalesce. Perhaps on other forms of input? Navigations? Timeout?

More generally, I think the text should increase while the tooltip is shown, and be reset once the tooltip is hidden. wdyt?

await page.fill('username', 'John Doe');
await page.check('remember be');
await page.fill('password', 'foo');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think theres a lot we can do by taking into account the specific method that called into the keyboard. I was looking at a karaoke style https://youtu.be/OU3699R53rs?t=23 for page.type. And we can remove the indicator on page.fill if its filling uninterrupted into a textarea. It's just redundant there.

I'd rather tackle these in a follow up patch because they involve a lot of api changes and bikeshedding.

docs/src/api/class-browsertype.md Show resolved Hide resolved
src/debug/showUserInput.ts Outdated Show resolved Hide resolved
src/server/contextListeners.ts Outdated Show resolved Hide resolved
@@ -197,6 +197,11 @@ Specify environment variables that will be visible to the browser. Defaults to `

Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.

### option: BrowserType.launch.showUserInput
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am hesitant with a public option. We are growing a list of options nobody will ever know about. Let's think of "one to rule them all" approach?

@@ -290,6 +295,14 @@ Specify environment variables that will be visible to the browser. Defaults to `
Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
Defaults to 0.

### option: BrowserType.launchPersistentContext.showUserInput
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will I see these indicators on the screenshots? Does that mean all my screenshot tests will just fail? Let's hide them when screenshotting?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgozman as a counter argument to this, what if I want indicators in my screenshots? To make it more obvious what the screenshot is demonstrating?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I would prefer to have them as well

export class ShowUserInput implements ContextListener {
async onContextCreated(context: BrowserContext) {
context._inputListeners.add(async (page, event) => {
if (!page.context()._browser.options.showUserInput)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just not add the input listener in the first place.

@@ -332,7 +334,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
downloadsPath?: string,
chromiumSandbox?: boolean,
slowMo?: number,
noDefaultViewport?: boolean,
showUserInput?: boolean,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • what happened to the noDefaultViewport?
  • I would not want to add another API like headless: false that requires modifications of source for every debugging session.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are missing some doc checks these days.

@@ -322,7 +308,7 @@ export abstract class BrowserContext extends EventEmitter {

// Bookkeeping.
for (const listener of this._browser.options.contextListeners)
await listener.onContextDidDestroy(this);
await listener.onContextDidDestroy?.(this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we do this ?. already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of this patch yes. See previous comments about tsconfig.json

@@ -57,7 +57,15 @@ export class Keyboard {
if (kModifiers.includes(description.key as types.KeyboardModifier))
this._pressedModifiers.add(description.key as types.KeyboardModifier);
const text = description.text;
await this._raw.keydown(this._pressedModifiers, description.code, description.keyCode, description.keyCodeWithoutLocation, description.key, description.location, autoRepeat, text);
await Promise.all([
this._page.context().notifyInputListeners(this._page, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now conflicts with both pw:api and trace viewer. I wish we could have it all aligned.

@pavelfeldman
Copy link
Member

Closing this in favor of the new PWDEBUG=1 behavior that has already landed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants