Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions extension/src/frontend/view/Comment/Comment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { nanoid } from 'nanoid'
import { useCallback, useState } from 'react'

import { getTabId } from '../../utils'
import { useGlobalClass } from '../GlobalStyles'
import { useStudioClient } from '../StudioClientProvider'
import { useEscape } from '../hooks/useEscape'
import { usePreventClick } from '../hooks/usePreventClick'

import { CommentPopover } from './CommentPopover'

interface CommentProps {
onClose: () => void
}

export function Comment({ onClose }: CommentProps) {
const client = useStudioClient()
const [showPopover, setShowPopover] = useState(false)

useGlobalClass('commenting')

useEscape(() => {
if (showPopover) {
setShowPopover(false)
return
}
onClose()
}, [showPopover, onClose])

usePreventClick({
callback: () => {
setShowPopover(true)
},
})

const handleSubmit = useCallback(
(text: string) => {
client.send({
type: 'record-events',
events: [
{
type: 'comment',
eventId: nanoid(),
timestamp: Date.now(),
tab: getTabId(),
text,
},
],
})
setShowPopover(false)
onClose()
},
[client, onClose]
)

return (
<CommentPopover
open={showPopover}
onClose={() => setShowPopover(false)}
onSubmit={handleSubmit}
/>
)
}
128 changes: 128 additions & 0 deletions extension/src/frontend/view/Comment/CommentPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { css } from '@emotion/react'
import { SendHorizontalIcon } from 'lucide-react'
import { useEffect, useRef, useState } from 'react'

import { Flex } from '@/components/primitives/Flex'
import { IconButton } from '@/components/primitives/IconButton'
import { Input } from '@/components/primitives/Input'

interface CommentPopoverProps {
open: boolean
onClose: () => void
onSubmit: (text: string) => void
}

export function CommentPopover({
open,
onClose,
onSubmit,
}: CommentPopoverProps) {
const [text, setText] = useState('')
const inputRef = useRef<HTMLInputElement>(null)

useEffect(() => {
if (open) {
inputRef.current?.focus()
} else {
setText('')
}
}, [open])

if (!open) return null

const handleSubmit = () => {
const value = text.trim()
if (value) {
onSubmit(value)
onClose()
}
}

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault()
handleSubmit()
}
}

return (
<>
<div
css={css`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
z-index: calc(var(--studio-layer-2) - 1);
backdrop-filter: blur(2px);
`}
onClick={onClose}
/>
<div
css={css`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 320px;
background: var(--gray-1);
border: 1px solid var(--gray-7);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: var(--studio-layer-2);
`}
>
<Flex gap="0" align="center">
<Input
ref={inputRef}
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Add a comment..."
css={css`
flex: 1;
background: transparent;
padding: 0;
margin: 0;
border: none;

> input {
padding: 13px;
border: none;
outline: none;

&:focus {
outline: none;
box-shadow: none;
}
}
`}
/>
<IconButton
css={css`
width: 44px;
height: 44px;
border-radius: 0;
color: ${text.trim() ? 'var(--orange-11)' : 'var(--gray-9)'};

&:hover:not(:disabled) {
color: var(--orange-12);
}

&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
`}
disabled={!text.trim()}
onClick={handleSubmit}
>
<SendHorizontalIcon size={16} />
</IconButton>
</Flex>
</div>
</>
)
}
1 change: 1 addition & 0 deletions extension/src/frontend/view/Comment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Comment } from './Comment'
6 changes: 5 additions & 1 deletion extension/src/frontend/view/GlobalStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect } from 'react'

const uuid = nanoid()

type GlobalClass = 'inspecting' | 'asserting-text'
type GlobalClass = 'inspecting' | 'asserting-text' | 'commenting'

export function useGlobalClass(name: GlobalClass) {
useEffect(() => {
Expand Down Expand Up @@ -37,6 +37,10 @@ export function GlobalStyles() {
cursor: text !important;
user-select: text !important;
}

.ksix-studio-commenting-${uuid} {
cursor: text !important;
}
`}
/>
)
Expand Down
2 changes: 2 additions & 0 deletions extension/src/frontend/view/InBrowserControls.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from 'react'

import { Comment } from './Comment'
import { ElementInspector } from './ElementInspector'
import { EventDrawer } from './EventDrawer'
import { RemoteHighlights } from './RemoteHighlights'
Expand Down Expand Up @@ -33,6 +34,7 @@ export function InBrowserControls() {
{tool === 'assert-text' && (
<TextSelectionPopover onClose={handleDeselectTool} />
)}
{tool === 'add-comment' && <Comment onClose={handleDeselectTool} />}
<ToolBox
isDrawerOpen={isDrawerOpen}
tool={tool}
Expand Down
7 changes: 7 additions & 0 deletions extension/src/frontend/view/ToolBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
SquareDashedMousePointerIcon,
SquareStopIcon,
TextCursorIcon,
MessageCircle,
} from 'lucide-react'

import { Flex } from '@/components/primitives/Flex'
Expand Down Expand Up @@ -78,6 +79,7 @@ export function ToolBox({
switch (value) {
case 'assert-text':
case 'inspect':
case 'add-comment':
onSelectTool(value)
break

Expand Down Expand Up @@ -150,6 +152,11 @@ export function ToolBox({
<TextCursorIcon />
</Toolbar.ToggleItem>
</ToolBoxTooltip>
<ToolBoxTooltip content="Click anywhere on the page to add a comment">
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

To update this content description

<Toolbar.ToggleItem value="add-comment">
<MessageCircle />
</Toolbar.ToggleItem>
</ToolBoxTooltip>
</Toolbar.ToggleGroup>
<Toolbar.Separator />
<Toolbar.ToggleGroup
Expand Down
2 changes: 1 addition & 1 deletion extension/src/frontend/view/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ export interface Bounds extends Position {
height: number
}

export type Tool = 'inspect' | 'assert-text'
export type Tool = 'inspect' | 'assert-text' | 'add-comment'
21 changes: 21 additions & 0 deletions src/codegen/browser/__snapshots__/browser/comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Generated by Grafana k6 Studio (0.0.0-vitest) on 2023-10-01T00:00:00.000Z

import { browser } from "k6/browser";

export const options = {
scenarios: {
default: {
executor: "shared-iterations",
options: { browser: { type: "chromium" } },
},
},
};

export default async function () {
const page = await browser.newPage();

await page.goto("https://example.com");

// Expecto Patronum! This test now magically passes
await page.locator("button").click();
}
3 changes: 3 additions & 0 deletions src/codegen/browser/code/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ function isBrowserScenario(scenario: ir.Scenario) {
case 'ExpectExpression':
return visit(node.actual) || visitAssertion(node.expected)

case 'Comment':
return false

default:
return exhaustive(node)
}
Expand Down
4 changes: 4 additions & 0 deletions src/codegen/browser/code/scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { exhaustive } from '@/utils/typescript'
import { spaceBetween } from '../formatting/spacing'
import * as ir from '../intermediate/ast'

import { comment } from './comment'
import { ScenarioContext } from './context'

function emitNewPageExpression(
Expand Down Expand Up @@ -489,6 +490,9 @@ function emitStatement(
],
})

case 'Comment':
return comment(statement.value)

default:
return exhaustive(statement)
}
Expand Down
57 changes: 57 additions & 0 deletions src/codegen/browser/codegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,63 @@ it('should assert that aria input is not checked', async ({ expect }) => {
)
})

it('should emit comments in the script', async ({ expect }) => {
const script = await emitScript({
defaultScenario: {
nodes: [
{
type: 'page',
nodeId: 'page',
},
{
type: 'goto',
nodeId: 'goto',
url: 'https://example.com',
source: 'address-bar',
inputs: {
page: { nodeId: 'page' },
},
},
{
type: 'comment',
nodeId: 'comment-1',
text: 'Expecto Patronum! This test now magically passes',
inputs: {
previous: { nodeId: 'goto' },
},
},
{
type: 'locator',
nodeId: 'locator',
selector: { type: 'css', selector: 'button' },
inputs: {
page: { nodeId: 'page' },
},
},
{
type: 'click',
nodeId: 'click',
button: 'left',
modifiers: {
ctrl: false,
shift: false,
alt: false,
meta: false,
},
inputs: {
previous: { nodeId: 'comment-1' },
locator: { nodeId: 'locator' },
page: { nodeId: 'page' },
},
},
],
},
scenarios: {},
})

await expect(script).toMatchFileSnapshot('__snapshots__/browser/comments.ts')
})

it('should assert that aria input is indeterminate', async ({ expect }) => {
const script = await emitScript({
defaultScenario: {
Expand Down
Loading