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
26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,32 @@ on:
required: false

jobs:
playwright:
name: Playwright
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: '.nvmrc'
cache: 'yarn'

- name: Install
run: yarn install --immutable

- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: build-19
path: packages

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Run Playwright tests
run: yarn test:pw playwright/test/ --project chromium

cypress:
name: Cypress
runs-on: ubuntu-latest
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ debug-storybook.log
.vscode
.cursor/rules/nx-rules.mdc
.github/instructions/nx.instructions.md

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ const config = tseslint.config(
},
},
{
files: ['**/*.cy.ts', '**/*.cy.tsx'],
files: ['**/*.cy.ts', '**/*.cy.tsx', '**/*.spec.ts', '**/*.spec.tsx', 'playwright/**/*'],

plugins: {
'no-only-tests': noOnlyTests,
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"test:prepare": "rimraf temp && lerna run build",
"test:open": "CYPRESS_COVERAGE=false cypress open --component --browser chrome",
"test": "yarn test:prepare && cypress run --component --browser chrome --spec packages",
"test:pw": "playwright test -c playwright-ct.config.ts",
"test:pw:open": "playwright test -c playwright-ct.config.ts --ui",
"clean": "tsc --build --clean && tsc --build tsconfig.build.json --clean && rimraf temp .out && lerna run clean",
"clean:remove-modules": "yarn clean && rimraf node_modules",
"prettier:all": "prettier --write --config ./prettier.config.js \"**/*\"",
Expand Down Expand Up @@ -60,6 +62,8 @@
"@cypress/code-coverage": "4.0.0",
"@eslint/compat": "2.0.2",
"@eslint/js": "9.39.3",
"@playwright/experimental-ct-react": "1.58.2",
"@playwright/test": "1.58.2",
"@semantic-release/github": "12.0.6",
"@testing-library/cypress": "10.1.0",
"@types/jscodeshift": "17.3.0",
Expand Down
43 changes: 43 additions & 0 deletions playwright-ct.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { fileURLToPath } from 'node:url';
import { defineConfig, devices } from '@playwright/experimental-ct-react';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
testDir: '.',
testMatch: ['**/packages/main/src/components/**/test/*.spec.tsx', '**/playwright/test/*.spec.tsx'],
testIgnore: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx', '**/*.mdx'],
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
trace: 'on-first-retry',
ctViteConfig: {
plugins: [
react(),
tsconfigPaths({
projects: [fileURLToPath(new URL('tsconfig.base.json', import.meta.url))],
}),
],
optimizeDeps: {
esbuildOptions: {
target: 'esnext',
},
exclude: ['**/*.cy.tsx', '**/*.cy.ts', '**/*.stories.tsx'],
},
build: {
target: 'esnext',
rollupOptions: {
external: [/\.cy\.tsx$/, /\.cy\.ts$/, /\.stories\.tsx$/],
},
},
},
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
17 changes: 17 additions & 0 deletions playwright/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playwright Component Tests</title>
<script data-ui5-config type="application/json">
{
"theme": "sap_horizon"
}
</script>
</head>
<body>
<script type="module" src="./index.tsx"></script>
<div id="root"></div>
</body>
</html>
1 change: 1 addition & 0 deletions playwright/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@ui5/webcomponents-react/dist/Assets.js';
251 changes: 251 additions & 0 deletions playwright/test/UI5FixturesTestComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import { Button } from '@ui5/webcomponents-react/Button';
import { CheckBox } from '@ui5/webcomponents-react/CheckBox';
import { ComboBox } from '@ui5/webcomponents-react/ComboBox';
import { ComboBoxItem } from '@ui5/webcomponents-react/ComboBoxItem';
import { Dialog } from '@ui5/webcomponents-react/Dialog';
import { Input } from '@ui5/webcomponents-react/Input';
import { List } from '@ui5/webcomponents-react/List';
import { ListItemStandard } from '@ui5/webcomponents-react/ListItemStandard';
import { MultiComboBox } from '@ui5/webcomponents-react/MultiComboBox';
import { MultiComboBoxItem } from '@ui5/webcomponents-react/MultiComboBoxItem';
import { MultiInput } from '@ui5/webcomponents-react/MultiInput';
import { Option } from '@ui5/webcomponents-react/Option';
import { RadioButton } from '@ui5/webcomponents-react/RadioButton';
import { Select } from '@ui5/webcomponents-react/Select';
import { SuggestionItem } from '@ui5/webcomponents-react/SuggestionItem';
import { Switch } from '@ui5/webcomponents-react/Switch';
import { Tab } from '@ui5/webcomponents-react/Tab';
import { TabContainer } from '@ui5/webcomponents-react/TabContainer';
import { TextArea } from '@ui5/webcomponents-react/TextArea';
import { Toolbar } from '@ui5/webcomponents-react/Toolbar';
import { ToolbarButton } from '@ui5/webcomponents-react/ToolbarButton';
import { useState } from 'react';

export const InputTestComp = () => {
const [value, setValue] = useState('');
return (
<>
<Input data-testid="test-input" onInput={(e) => setValue(e.target.value)} />
<span data-testid="input-value">{value}</span>
</>
);
};

export const ClearInputTestComp = () => {
const [value, setValue] = useState('initial value');
return (
<>
<Input data-testid="test-input" onInput={(e) => setValue(e.target.value)} />
<span data-testid="input-value">{value}</span>
</>
);
};

export const CheckboxTestComp = () => {
const [checked, setChecked] = useState(false);
return (
<div>
<CheckBox data-testid="test-checkbox" onChange={(e) => setChecked(e.target.checked)} />
<span data-testid="checkbox-state">{checked ? 'checked' : 'unchecked'}</span>
</div>
);
};

export const SwitchTestComp = () => {
const [checked, setChecked] = useState(false);
return (
<div>
<Switch data-testid="test-switch" onChange={(e) => setChecked(e.target.checked)} />
<span data-testid="switch-state">{checked ? 'on' : 'off'}</span>
</div>
);
};

export const RadioButtonTestComp = () => {
const [selected, setSelected] = useState('');
return (
<div>
<RadioButton data-testid="radio-1" name="group" text="Option 1" onChange={() => setSelected('option1')} />
<RadioButton data-testid="radio-2" name="group" text="Option 2" onChange={() => setSelected('option2')} />
<span data-testid="radio-state">{selected}</span>
</div>
);
};

export const TextAreaTestComp = () => {
const [value, setValue] = useState('');
return (
<div>
<TextArea data-testid="test-textarea" onInput={(e) => setValue(e.target.value)} />
<span data-testid="textarea-value">{value}</span>
</div>
);
};

export const DialogTestComp = () => {
const [open, setOpen] = useState(true);
return (
<>
<Dialog data-testid="test-dialog" open={open} onClose={() => setOpen(false)} headerText="Test Dialog">
<p>Dialog content</p>
</Dialog>
<span data-testid="dialog-state">{open ? 'open' : 'closed'}</span>
</>
);
};

export const AttributeTestComp = () => {
return <Button data-testid="test-button">Click me</Button>;
};

export const ListTestComp = () => {
const [selectedItem, setSelectedItem] = useState('');
return (
<div>
<List
data-testid="test-list"
selectionMode="Single"
onSelectionChange={(e) => {
const item = e.detail.selectedItems[0];
if (item) {
setSelectedItem(item.getAttribute('text') || '');
}
}}
>
<ListItemStandard text="First Item" />
<ListItemStandard text="Second Item" />
<ListItemStandard text="Third Item" />
</List>
<span data-testid="selected-item">{selectedItem}</span>
</div>
);
};

export const SelectTestComp = () => {
const [selectedValue, setSelectedValue] = useState('');
return (
<div>
<Select data-testid="test-select" onChange={(e) => setSelectedValue(e.detail.selectedOption?.textContent || '')}>
<Option>Option A</Option>
<Option>Option B</Option>
<Option>Option C</Option>
</Select>
<span data-testid="selected-value">{selectedValue}</span>
</div>
);
};

export const ComboBoxTestComp = () => {
const [value, setValue] = useState('');
return (
<div>
<ComboBox data-testid="test-combobox" onSelectionChange={(e) => setValue(e.detail.item?.text || '')}>
<ComboBoxItem text="Apple" />
<ComboBoxItem text="Banana" />
<ComboBoxItem text="Cherry" />
</ComboBox>
<span data-testid="combobox-value">{value}</span>
</div>
);
};

export const MultiComboBoxTestComp = () => {
const [selectedItems, setSelectedItems] = useState<string[]>([]);
return (
<div>
<MultiComboBox
data-testid="test-multicombobox"
onSelectionChange={(e) => {
const items = e.detail.items.map((item) => item.text);
setSelectedItems(items);
}}
>
<MultiComboBoxItem text="Red" />
<MultiComboBoxItem text="Green" />
<MultiComboBoxItem text="Blue" />
</MultiComboBox>
<span data-testid="multicombobox-values">{selectedItems.join(', ')}</span>
</div>
);
};

export const ToolbarTestComp = () => {
const [clickedButton, setClickedButton] = useState('');
return (
<div>
<Toolbar data-testid="test-toolbar">
<ToolbarButton text="Save" onClick={() => setClickedButton('Save')} />
<ToolbarButton text="Edit" onClick={() => setClickedButton('Edit')} />
<ToolbarButton text="Delete" onClick={() => setClickedButton('Delete')} />
</Toolbar>
<span data-testid="clicked-button">{clickedButton}</span>
</div>
);
};

export const TabContainerTestComp = () => {
const [selectedTab, setSelectedTab] = useState('Tab 1');
return (
<div>
<TabContainer data-testid="test-tabcontainer" onTabSelect={(e) => setSelectedTab(e.detail.tab.text || '')}>
<Tab text="Tab 1">Content 1</Tab>
<Tab text="Tab 2">Content 2</Tab>
<Tab text="Tab 3">Content 3</Tab>
</TabContainer>
<span data-testid="selected-tab">{selectedTab}</span>
</div>
);
};

export const TabContainerWithNestedTabsTestComp = () => {
return (
<TabContainer data-testid="test-tabcontainer-nested">
<Tab text="Tab 1">Content 1</Tab>
<Tab
text="Tab 2"
items={
<>
<Tab text="Tab 2.1">Content 2.1</Tab>
<Tab text="Tab 2.2">Content 2.2</Tab>
</>
}
>
Content 2
</Tab>
</TabContainer>
);
};

export const InputWithDelayTestComp = () => {
const [value, setValue] = useState('');
return (
<div>
<ComboBox data-testid="test-combobox-delay" onInput={(e) => setValue(e.target.value || '')}>
<ComboBoxItem text="Suggestion 1" />
<ComboBoxItem text="Suggestion 2" />
<ComboBoxItem text="Suggestion 3" />
</ComboBox>
<span data-testid="delay-input-value">{value}</span>
</div>
);
};

export const InputWithSuggestionsTestComp = () => {
return (
<Input showSuggestions data-testid="test-input-suggestions">
<SuggestionItem text="Suggestion A" />
<SuggestionItem text="Suggestion B" />
<SuggestionItem text="Other Item" />
</Input>
);
};

export const MultiInputWithSuggestionsTestComp = () => {
return (
<MultiInput showSuggestions data-testid="test-multiinput-suggestions">
<SuggestionItem text="Suggestion X" />
<SuggestionItem text="Suggestion Y" />
<SuggestionItem text="Suggestion Z" />
</MultiInput>
);
};
Loading
Loading