Compare commits

...

5 Commits

Author SHA1 Message Date
Rıdvan Akca
9752e2c4d2 test: rewrite flow editor tests with playwright 2023-08-17 15:34:56 +03:00
Rıdvan Akca
a5b31da3cc test: rewrite executions tests with playwright (#1207) 2023-08-17 11:40:46 +03:00
Rıdvan Akca
8f7785e9d2 test: rewrite connections tests with playwright (#1203) 2023-08-17 11:40:46 +03:00
Rıdvan Akca
69297c2dd8 test: migrate apps folder to playwright (#1201) 2023-08-17 11:40:46 +03:00
Rıdvan Akca
1c8e9fac7c feat: introduce playwright 2023-08-17 11:40:46 +03:00
20 changed files with 670 additions and 11 deletions

25
.github/workflows/playwright.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Automatisch UI Test
on:
schedule:
- cron: '0 12 * * *'
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run Playwright tests
run: yarn playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

5
packages/e2e-tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/
/output

View File

@@ -0,0 +1,12 @@
const path = require('node:path');
const { BasePage } = require('./base-page');
export class ApplicationsPage extends BasePage {
async screenshot(options = {}) {
const { path: plainPath, ...restOptions } = options;
const computedPath = path.join('applications', plainPath);
return await super.screenshot({ path: computedPath, ...restOptions });
}
}

View File

@@ -0,0 +1,34 @@
const path = require('node:path');
export class BasePage {
/**
* @param {import('@playwright/test').Page} page
*/
constructor(page) {
this.page = page;
}
async clickAway() {
await this.page.locator('body').click({ position: { x: 0, y: 0 } });
}
async screenshot(options = {}) {
const { path: plainPath, ...restOptions } = options;
const computedPath = path.join('output/screenshots', plainPath);
return await this.page.screenshot({ path: computedPath, ...restOptions });
}
async login() {
await this.page.goto('/login');
await this.page
.getByTestId('email-text-field')
.fill(process.env.LOGIN_EMAIL);
await this.page
.getByTestId('password-text-field')
.fill(process.env.LOGIN_PASSWORD);
await this.page.getByTestId('login-button').click();
}
}

View File

@@ -0,0 +1,16 @@
const path = require('node:path');
const { BasePage } = require('./base-page');
export class ConnectionsPage extends BasePage {
async screenshot(options = {}) {
const { path: plainPath, ...restOptions } = options;
const computedPath = path.join('connections', plainPath);
return await super.screenshot({ path: computedPath, ...restOptions });
}
async clickAddConnectionButton() {
await this.page.getByTestId('add-connection-button').click();
}
}

View File

@@ -0,0 +1,12 @@
const path = require('node:path');
const { BasePage } = require('./base-page');
export class ExecutionsPage extends BasePage {
async screenshot(options = {}) {
const { path: plainPath, ...restOptions } = options;
const computedPath = path.join('executions', plainPath);
return await super.screenshot({ path: computedPath, ...restOptions });
}
}

View File

@@ -0,0 +1,27 @@
const path = require('node:path');
const { BasePage } = require('./base-page');
export class FlowEditorPage extends BasePage {
constructor(page) {
super(page);
this.appAutocomplete = this.page.getByTestId('choose-app-autocomplete');
this.eventAutocomplete = this.page.getByTestId('choose-event-autocomplete');
this.continueButton = this.page.getByTestId('flow-substep-continue-button');
this.connectionAutocomplete = this.page.getByTestId(
'choose-connection-autocomplete'
);
this.testOuput = this.page.getByTestId('flow-test-substep-output');
this.unpublishFlowButton = this.page.getByTestId('unpublish-flow-button');
this.publishFlowButton = this.page.getByTestId('publish-flow-button');
this.infoSnackbar = this.page.getByTestId('flow-cannot-edit-info-snackbar');
this.trigger = this.page.getByLabel('Trigger on weekends?');
}
async screenshot(options = {}) {
const { path: plainPath, ...restOptions } = options;
const computedPath = path.join('flow-editor', plainPath);
return await super.screenshot({ path: computedPath, ...restOptions });
}
}

View File

@@ -0,0 +1,21 @@
const base = require('@playwright/test');
const { ApplicationsPage } = require('./applications-page');
const { ConnectionsPage } = require('./connections-page');
const { ExecutionsPage } = require('./executions-page');
const { FlowEditorPage } = require('./flow-editor-page');
exports.test = base.test.extend({
applicationsPage: async ({ page }, use) => {
await use(new ApplicationsPage(page));
},
connectionsPage: async ({ page }, use) => {
await use(new ConnectionsPage(page));
},
executionsPage: async ({ page }, use) => {
await use(new ExecutionsPage(page));
},
flowEditorPage: async ({ page }, use) => {
await use(new FlowEditorPage(page));
},
});
exports.expect = base.expect;

View File

@@ -5,7 +5,8 @@
"private": true, "private": true,
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.", "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": { "scripts": {
"open": "cypress open" "open": "cypress open",
"playwright": "playwright test"
}, },
"contributors": [ "contributors": [
{ {
@@ -22,6 +23,10 @@
"url": "https://github.com/automatisch/automatisch/issues" "url": "https://github.com/automatisch/automatisch/issues"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.36.2",
"cypress": "^10.9.0" "cypress": "^10.9.0"
},
"dependencies": {
"dotenv": "^16.3.1"
} }
} }

View File

@@ -0,0 +1,82 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test');
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require('dotenv').config();
/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? 'github' : 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: process.env.CI
? 'https://sandbox.automatisch.io'
: 'http://localhost:3001',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
testIdAttribute: 'data-test',
viewport: { width: 1280, height: 720 },
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});

View File

@@ -0,0 +1,67 @@
// @ts-check
const { test, expect } = require('../../fixtures/index');
test.describe('Apps page', () => {
test.beforeEach(async ({ page, applicationsPage }) => {
await applicationsPage.login();
await page.getByTestId('apps-page-drawer-link').click();
});
test('displays applications', async ({ page, applicationsPage }) => {
await page.getByTestId('apps-loader').waitFor({
state: 'detached',
});
await expect(page.getByTestId('app-row')).not.toHaveCount(0);
await applicationsPage.screenshot({
path: 'Applications.png',
});
});
test.describe('can add connection', () => {
test.beforeEach(async ({ page }) => {
await expect(page.getByTestId('add-connection-button')).toBeVisible();
await page.getByTestId('add-connection-button').click();
await page
.getByTestId('search-for-app-loader')
.waitFor({ state: 'detached' });
});
test('lists applications', async ({ page, applicationsPage }) => {
const appListItemCount = await page.getByTestId('app-list-item').count();
expect(appListItemCount).toBeGreaterThan(10);
await applicationsPage.clickAway();
});
test('searches an application', async ({ page, applicationsPage }) => {
await page.getByTestId('search-for-app-text-field').fill('DeepL');
await expect(page.getByTestId('app-list-item')).toHaveCount(1);
await applicationsPage.clickAway();
});
test('goes to app page to create a connection', async ({
page,
applicationsPage,
}) => {
await page.getByTestId('app-list-item').first().click();
await expect(page).toHaveURL('/app/deepl/connections/add');
await expect(page.getByTestId('add-app-connection-dialog')).toBeVisible();
await applicationsPage.clickAway();
});
test('closes the dialog on backdrop click', async ({
page,
applicationsPage,
}) => {
await page.getByTestId('app-list-item').first().click();
await expect(page).toHaveURL('/app/deepl/connections/add');
await expect(page.getByTestId('add-app-connection-dialog')).toBeVisible();
await applicationsPage.clickAway();
await expect(page).toHaveURL('/app/deepl/connections');
await expect(page.getByTestId('add-app-connection-dialog')).toBeHidden();
});
});
});

View File

@@ -0,0 +1,50 @@
// @ts-check
const { test, expect } = require('../../fixtures/index');
test.describe('Connections page', () => {
test.beforeEach(async ({ page, connectionsPage }) => {
await connectionsPage.login();
await page.getByTestId('apps-page-drawer-link').click();
await page.goto('/app/ntfy/connections');
});
test('shows connections if any', async ({ page, connectionsPage }) => {
await page.getByTestId('apps-loader').waitFor({
state: 'detached',
});
await connectionsPage.screenshot({
path: 'Connections.png',
});
});
test.describe('can add connection', () => {
test('has a button to open add connection dialog', async ({ page }) => {
await expect(page.getByTestId('add-connection-button')).toBeVisible();
});
test('add connection button takes user to add connection page', async ({
page,
connectionsPage,
}) => {
await connectionsPage.clickAddConnectionButton();
await expect(page).toHaveURL('/app/ntfy/connections/add');
});
test('shows add connection dialog to create a new connection', async ({
page,
connectionsPage,
}) => {
await connectionsPage.clickAddConnectionButton();
await expect(page).toHaveURL('/app/ntfy/connections/add');
await page.getByTestId('create-connection-button').click();
await expect(
page.getByTestId('create-connection-button')
).not.toBeVisible();
await connectionsPage.screenshot({
path: 'Ntfy connections after creating a connection.png',
});
});
});
});

View File

@@ -0,0 +1,39 @@
// @ts-check
const { test, expect } = require('../../fixtures/index');
test.describe('Executions page', () => {
test.beforeEach(async ({ page, executionsPage }) => {
await executionsPage.login();
await page.getByTestId('executions-page-drawer-link').click();
await page.getByTestId('execution-row').first().click();
await expect(page).toHaveURL(/\/executions\//);
});
test('displays data in by default', async ({ page, executionsPage }) => {
await expect(page.getByTestId('execution-step').last()).toBeVisible();
await expect(page.getByTestId('execution-step')).toHaveCount(2);
await executionsPage.screenshot({
path: 'Execution - data in.png',
});
});
test('displays data out', async ({ page, executionsPage }) => {
const executionStepCount = await page.getByTestId('execution-step').count();
for (let i = 0; i < executionStepCount; i++) {
await page.getByTestId('data-out-tab').nth(i).click();
await expect(page.getByTestId('data-out-panel').nth(i)).toBeVisible();
await executionsPage.screenshot({
path: `Execution - data out - ${i}.png`,
animations: 'disabled',
});
}
});
test('does not display error', async ({ page }) => {
await expect(page.getByTestId('error-tab')).toBeHidden();
});
});

View File

@@ -0,0 +1,19 @@
// @ts-check
const { test, expect } = require('../../fixtures/index');
test.describe('Executions page', () => {
test.beforeEach(async ({ page, executionsPage }) => {
await executionsPage.login();
await page.getByTestId('executions-page-drawer-link').click();
});
test('displays executions', async ({ page, executionsPage }) => {
await page.getByTestId('executions-loader').waitFor({
state: 'detached',
});
await expect(page.getByTestId('execution-row').first()).toBeVisible();
await executionsPage.screenshot({ path: 'Executions.png' });
});
});

View File

@@ -0,0 +1,205 @@
// @ts-check
const { FlowEditorPage } = require('../../fixtures/flow-editor-page');
const { test, expect } = require('../../fixtures/index');
test.describe.configure({ mode: 'serial' });
let page;
let flowEditorPage;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
flowEditorPage = new FlowEditorPage(page);
});
test('create flow', async ({}) => {
await flowEditorPage.login();
await flowEditorPage.page.getByTestId('create-flow-button').click();
await expect(flowEditorPage.page).toHaveURL(/\/editor\/create/);
await expect(flowEditorPage.page).toHaveURL(
/\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/
);
});
test('has two steps by default', async ({}) => {
await expect(flowEditorPage.page.getByTestId('flow-step')).toHaveCount(2);
});
test.describe('arrange Scheduler trigger', () => {
test.describe('choose app and event substep', () => {
test('choose application', async ({}) => {
await flowEditorPage.appAutocomplete.click();
await flowEditorPage.page
.getByRole('option', { name: 'Scheduler' })
.click();
});
test('choose an event', async ({}) => {
await expect(flowEditorPage.eventAutocomplete).toBeVisible();
await flowEditorPage.eventAutocomplete.click();
await flowEditorPage.page
.getByRole('option', { name: 'Every hour' })
.click();
});
test('continue to next step', async ({}) => {
await flowEditorPage.continueButton.click();
});
test('collapses the substep', async ({}) => {
await expect(flowEditorPage.appAutocomplete).not.toBeVisible();
await expect(flowEditorPage.eventAutocomplete).not.toBeVisible();
});
});
test.describe('set up a trigger', () => {
test('choose "yes" in "trigger on weekends?"', async ({}) => {
await expect(flowEditorPage.trigger).toBeVisible();
await flowEditorPage.trigger.click();
await flowEditorPage.page.getByRole('option', { name: 'Yes' }).click();
});
test('continue to next step', async ({}) => {
await flowEditorPage.continueButton.click();
});
test('collapses the substep', async ({}) => {
await expect(flowEditorPage.trigger).not.toBeVisible();
});
});
test.describe('test trigger', () => {
test('show sample output', async ({}) => {
await expect(flowEditorPage.testOuput).not.toBeVisible();
await flowEditorPage.continueButton.click();
await expect(flowEditorPage.testOuput).toBeVisible();
await flowEditorPage.screenshot({
path: 'Scheduler trigger test output.png',
});
await flowEditorPage.continueButton.click();
});
});
});
test.describe('arrange Ntfy action', () => {
test.describe('choose app and event substep', () => {
test('choose application', async ({}) => {
await flowEditorPage.appAutocomplete.click();
await flowEditorPage.page.getByRole('option', { name: 'Ntfy' }).click();
});
test('choose an event', async ({}) => {
await expect(flowEditorPage.eventAutocomplete).toBeVisible();
await flowEditorPage.eventAutocomplete.click();
await flowEditorPage.page
.getByRole('option', { name: 'Send message' })
.click();
});
test('continue to next step', async ({}) => {
await flowEditorPage.continueButton.click();
});
test('collapses the substep', async ({}) => {
await expect(flowEditorPage.appAutocomplete).not.toBeVisible();
await expect(flowEditorPage.eventAutocomplete).not.toBeVisible();
});
});
test.describe('choose connection', () => {
test('choose connection list item', async ({}) => {
await flowEditorPage.connectionAutocomplete.click();
await flowEditorPage.page.getByRole('listitem').first().click();
});
test('continue to next step', async ({}) => {
await flowEditorPage.continueButton.click();
});
test('collapses the substep', async ({}) => {
await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible();
});
});
test.describe('set up action', () => {
test('fill topic and message body', async ({}) => {
await flowEditorPage.page
.getByTestId('parameters.topic-power-input')
.locator('[contenteditable]')
.fill('Topic');
await flowEditorPage.page
.getByTestId('parameters.message-power-input')
.locator('[contenteditable]')
.fill('Message body');
});
test('continue to next step', async ({}) => {
await flowEditorPage.continueButton.click();
});
test('collapses the substep', async ({}) => {
await expect(flowEditorPage.connectionAutocomplete).not.toBeVisible();
});
});
test.describe('test trigger', () => {
test('show sample output', async ({}) => {
await expect(flowEditorPage.testOuput).not.toBeVisible();
await flowEditorPage.page
.getByTestId('flow-substep-continue-button')
.first()
.click();
await expect(flowEditorPage.testOuput).toBeVisible();
await flowEditorPage.screenshot({
path: 'Ntfy action test output.png',
});
await flowEditorPage.continueButton.click();
});
});
});
test.describe('publish and unpublish', () => {
test('publish flow', async ({}) => {
await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible();
await expect(flowEditorPage.publishFlowButton).toBeVisible();
await flowEditorPage.publishFlowButton.click();
await expect(flowEditorPage.publishFlowButton).not.toBeVisible();
});
test('shows read-only sticky snackbar', async ({}) => {
await expect(flowEditorPage.infoSnackbar).toBeVisible();
await flowEditorPage.screenshot({
path: 'Published flow.png',
});
});
test('unpublish from snackbar', async ({}) => {
await flowEditorPage.page
.getByTestId('unpublish-flow-from-snackbar')
.click();
await expect(flowEditorPage.infoSnackbar).not.toBeVisible();
});
test('publish once again', async ({}) => {
await expect(flowEditorPage.publishFlowButton).toBeVisible();
await flowEditorPage.publishFlowButton.click();
await expect(flowEditorPage.publishFlowButton).not.toBeVisible();
});
test('unpublish from layout top bar', async ({}) => {
await expect(flowEditorPage.unpublishFlowButton).toBeVisible();
await flowEditorPage.unpublishFlowButton.click();
await expect(flowEditorPage.unpublishFlowButton).not.toBeVisible();
await flowEditorPage.screenshot({
path: 'Unpublished flow.png',
});
});
});
test.describe('in layout', () => {
test('can go back to flows page', async ({}) => {
await flowEditorPage.page.getByTestId('editor-go-back-button').click();
await expect(flowEditorPage.page).toHaveURL('/flows');
});
});

View File

@@ -101,7 +101,9 @@ export default function AddNewAppConnection(
</InputAdornment> </InputAdornment>
} }
label={formatMessage('apps.searchApp')} label={formatMessage('apps.searchApp')}
data-test="search-for-app-text-field" inputProps={{
'data-test': 'search-for-app-text-field',
}}
/> />
</FormControl> </FormControl>
</Box> </Box>
@@ -109,7 +111,10 @@ export default function AddNewAppConnection(
<DialogContent> <DialogContent>
<List sx={{ pt: 2, width: '100%' }}> <List sx={{ pt: 2, width: '100%' }}>
{loading && ( {loading && (
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} /> <CircularProgress
data-test="search-for-app-loader"
sx={{ display: 'block', margin: '20px auto' }}
/>
)} )}
{!loading && {!loading &&

View File

@@ -37,10 +37,12 @@ function ExecutionStepDate(props: Pick<IExecutionStep, 'createdAt'>) {
const relativeCreatedAt = createdAt.toRelative(); const relativeCreatedAt = createdAt.toRelative();
return ( return (
<Tooltip title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}> <Tooltip
title={createdAt.toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS)}
>
<Typography variant="caption" gutterBottom> <Typography variant="caption" gutterBottom>
{formatMessage('executionStep.executedAt', { {formatMessage('executionStep.executedAt', {
datetime: relativeCreatedAt datetime: relativeCreatedAt,
})} })}
</Typography> </Typography>
</Tooltip> </Tooltip>
@@ -117,7 +119,7 @@ export default function ExecutionStep(
<SearchableJSONViewer data={executionStep.dataIn} /> <SearchableJSONViewer data={executionStep.dataIn} />
</TabPanel> </TabPanel>
<TabPanel value={activeTabIndex} index={1}> <TabPanel value={activeTabIndex} index={1} data-test="data-out-panel">
<SearchableJSONViewer data={executionStep.dataOut} /> <SearchableJSONViewer data={executionStep.dataOut} />
</TabPanel> </TabPanel>

View File

@@ -60,11 +60,13 @@ const PowerInput = (props: PowerInputProps) => {
const [showVariableSuggestions, setShowVariableSuggestions] = const [showVariableSuggestions, setShowVariableSuggestions] =
React.useState(false); React.useState(false);
const disappearSuggestionsOnShift = (event: React.KeyboardEvent<HTMLInputElement>) => { const disappearSuggestionsOnShift = (
event: React.KeyboardEvent<HTMLInputElement>
) => {
if (event.code === 'Tab') { if (event.code === 'Tab') {
setShowVariableSuggestions(false); setShowVariableSuggestions(false);
} }
} };
const stepsWithVariables = React.useMemo(() => { const stepsWithVariables = React.useMemo(() => {
return processStepWithExecutions(priorStepsWithExecutions); return processStepWithExecutions(priorStepsWithExecutions);
@@ -112,7 +114,10 @@ const PowerInput = (props: PowerInputProps) => {
}} }}
> >
{/* ref-able single child for ClickAwayListener */} {/* ref-able single child for ClickAwayListener */}
<ChildrenWrapper style={{ width: '100%' }} data-test="power-input"> <ChildrenWrapper
style={{ width: '100%' }}
data-test={`${name}-power-input`}
>
<FakeInput disabled={disabled}> <FakeInput disabled={disabled}>
<InputLabelWrapper> <InputLabelWrapper>
<InputLabel <InputLabel
@@ -140,7 +145,10 @@ const PowerInput = (props: PowerInputProps) => {
/> />
</FakeInput> </FakeInput>
{/* ghost placer for the variables popover */} {/* ghost placer for the variables popover */}
<div ref={editorRef} style={{ position: 'absolute', right: 16, left: 16 }} /> <div
ref={editorRef}
style={{ position: 'absolute', right: 16, left: 16 }}
/>
<Popper <Popper
open={showVariableSuggestions} open={showVariableSuggestions}

View File

@@ -14,6 +14,7 @@ type TextFieldProps = {
name: string; name: string;
clickToCopy?: boolean; clickToCopy?: boolean;
readOnly?: boolean; readOnly?: boolean;
'data-test'?: string;
} & MuiTextFieldProps; } & MuiTextFieldProps;
const createCopyAdornment = ( const createCopyAdornment = (
@@ -44,6 +45,7 @@ export default function TextField(props: TextFieldProps): React.ReactElement {
disabled = false, disabled = false,
onBlur, onBlur,
onChange, onChange,
'data-test': dataTest,
...textFieldProps ...textFieldProps
} = props; } = props;
@@ -82,6 +84,9 @@ export default function TextField(props: TextFieldProps): React.ReactElement {
readOnly, readOnly,
endAdornment: clickToCopy ? createCopyAdornment(inputRef) : null, endAdornment: clickToCopy ? createCopyAdornment(inputRef) : null,
}} }}
inputProps={{
'data-test': dataTest,
}}
/> />
)} )}
/> />

View File

@@ -3380,6 +3380,16 @@
dependencies: dependencies:
"@octokit/openapi-types" "^11.2.0" "@octokit/openapi-types" "^11.2.0"
"@playwright/test@^1.36.2":
version "1.36.2"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.36.2.tgz#9edd68a02b0929c5d78d9479a654ceb981dfb592"
integrity sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==
dependencies:
"@types/node" "*"
playwright-core "1.36.2"
optionalDependencies:
fsevents "2.3.2"
"@pmmmwh/react-refresh-webpack-plugin@^0.5.3": "@pmmmwh/react-refresh-webpack-plugin@^0.5.3":
version "0.5.4" version "0.5.4"
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99" resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.4.tgz#df0d0d855fc527db48aac93c218a0bf4ada41f99"
@@ -7961,6 +7971,11 @@ dotenv@^10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^16.3.1:
version "16.3.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
duplexer3@^0.1.4: duplexer3@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -9400,7 +9415,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@^2.3.2, fsevents@~2.3.2: fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -13928,6 +13943,11 @@ pkg-up@^3.1.0:
dependencies: dependencies:
find-up "^3.0.0" find-up "^3.0.0"
playwright-core@1.36.2:
version "1.36.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.36.2.tgz#32382f2d96764c24c65a86ea336cf79721c2e50e"
integrity sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==
plur@^4.0.0: plur@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84" resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84"