feat(wren-ui/e2e): Add calculated fields CRUD test for E-commerce sample dataset (#427)

* chore(e2e): update .env.test

* chore(e2e): update README.md for E2E

* feat(wren-ui/e2e): add CF test for e-commerce
This commit is contained in:
Freda Lai
2024-06-19 17:02:02 +08:00
committed by GitHub
parent ec1f7fa513
commit 8faebc76bb
7 changed files with 257 additions and 139 deletions
+2 -1
View File
@@ -1,2 +1,3 @@
DB_TYPE=sqlite
SQLITE_FILE=testdb.sqlite3
SQLITE_FILE=testdb.sqlite3
OTHER_SERVICE_USING_DOCKER=true
+38 -36
View File
@@ -1,49 +1,52 @@
## How to run e2e test locally
1. Make sure you have start all WrenAI services. ([How to start](https://github.com/Canner/WrenAI/blob/3234dc218b105caba04e1cdab7b1cb7140fc9b90/docker/README.md#how-to-start))
2. Create a `e2e.config.json` file under `wren-ui/e2e` folder and replace all data sources needed values in `./config.ts`.
```ts
// Replace the default test config with your own e2e.config.json
const defaultTestConfig = {
bigQuery: {
projectId: 'wrenai',
datasetId: 'wrenai.tpch_sf1',
// The credential file should be under "wren-ui" folder
// For example: .tmp/credential.json
credentialPath: 'bigquery-credential-path',
},
duckDb: {
sqlCsvPath: 'https://duckdb.org/data/flights.csv',
},
postgreSql: {
host: 'postgresql-host',
port: '5432',
username: 'postgresql-username',
password: 'postgresql-password',
database: 'postgresql-database',
ssl: false,
},
};
```
```ts
// Replace the default test config with your own e2e.config.json
const defaultTestConfig = {
bigQuery: {
projectId: 'wrenai',
datasetId: 'wrenai.tpch_sf1',
// The credential file should be under "wren-ui" folder
// For example: .tmp/credential.json
credentialPath: 'bigquery-credential-path',
},
duckDb: {
sqlCsvPath: 'https://duckdb.org/data/flights.csv',
},
postgreSql: {
host: 'postgresql-host',
port: '5432',
username: 'postgresql-username',
password: 'postgresql-password',
database: 'postgresql-database',
ssl: false,
},
};
```
3. Build UI before starting e2e server
```bash
yarn build
```
```bash
yarn build
```
> Ensure port 3000 is available for E2E testing. The AI service needs WREN_UI_ENDPOINT to connect to this port for accurate and reliable test results.
4. Run test
```bash
yarn test:e2e
```
```bash
yarn test:e2e
```
Run test with browser open
```bash
yarn test:e2e --headed
```
Run test with browser open
```bash
yarn test:e2e --headed
```
## How to develop
@@ -64,4 +67,3 @@
```
npx playwright codegen http://localhost:3000
```
+118 -6
View File
@@ -89,12 +89,7 @@ export const executeModelCRUD = async (
).toBeHidden();
// add the model back
await page
.locator('div')
.filter({ hasText: /^Models\(\d\)$/ })
.locator('path')
.first()
.click();
await page.getByTestId('add-model').click();
// chkeck Model drawer open
await expect(page.locator('.ant-drawer-mask')).toBeVisible();
@@ -546,3 +541,120 @@ export const updateViewMetadata = async (
await checkDeployUndeployedChanges({ page, baseURL });
};
export const addCalculatedField = async (
page: Page,
{
calculatedFieldName,
expression,
modelDisplayName,
toFieldModelDisplayName,
toFieldColumnDisplayName,
}: {
calculatedFieldName: string;
expression: string;
modelDisplayName: string;
toFieldModelDisplayName: string;
toFieldColumnDisplayName: string;
},
) => {
// click the model of sidebar to zoom in
await page
.getByRole('complementary')
.getByText(modelDisplayName, { exact: true })
.click();
// add calculated field
await page
.getByTestId(`diagram__model-node__${modelDisplayName}`)
.locator('div')
.filter({ hasText: /^Calculated Fields$/ })
.getByRole('button')
.first()
.click();
await expect(page.locator('.ant-modal-mask')).toBeVisible();
await expect(page.locator('div.ant-modal')).toBeVisible();
await expect(
page
.locator('div.ant-modal-title')
.filter({ hasText: 'Add calculated field' }),
).toBeVisible();
await expect(
page
.getByLabel('Add calculated field')
.getByLabel('Close', { exact: true }),
).toBeVisible();
await page.getByLabel('Name').click();
await page.getByLabel('Name').fill(calculatedFieldName);
await page.getByTestId('common__descriptive-select').click();
await page.getByTitle(expression).locator('div').click();
await expect(page.getByTestId('common__lineage')).toBeVisible();
await expect(
page
.getByTestId('common__lineage-field-block')
.getByText(modelDisplayName, { exact: true }),
).toBeVisible();
await page.getByTestId('common__lineage-fields-select').click();
// for skip disabled item
await page.getByTestId('common__lineage-fields-select').press('ArrowDown');
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldModelDisplayName })
.scrollIntoViewIfNeeded();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldModelDisplayName })
.click();
await expect(
page
.getByTestId('common__lineage-field-block')
.getByText(toFieldModelDisplayName, { exact: true }),
).toHaveCount(2);
await expect(page.getByText('Please select a field.')).toBeVisible();
await page.getByTestId('common__lineage-fields-select').last().click();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldColumnDisplayName })
.scrollIntoViewIfNeeded();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldColumnDisplayName })
.click();
await page.getByRole('button', { name: 'Save' }).click();
await expect(
page.getByText('Successfully created calculated field.'),
).toBeVisible();
};
export const deleteCalculatedField = async (
page: Page,
modelDisplayName: string,
) => {
// delete calculated field
await page
.getByRole('complementary')
.getByText(modelDisplayName, { exact: true })
.click();
await page
.getByTestId(`diagram__model-node__${modelDisplayName}`)
.getByRole('button', { name: 'more' })
.nth(1)
.click();
await page.getByText('Delete', { exact: true }).click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(
page.getByText('Successfully deleted calculated field.'),
).toBeVisible();
};
@@ -125,4 +125,85 @@ test.describe('Test E-commerce sample dataset', () => {
},
);
});
test('Calculated Fields CRUD successfully', async ({ page }) => {
await page.goto('/modeling');
await expect(page).toHaveURL('/modeling', { timeout: 60000 });
const modelDisplayName = 'Orders';
const calculatedFieldName = 'Sum of review scores';
const expression = 'Sum';
const toFieldModelDisplayName = 'reviews';
const toFieldColumnDisplayName = 'Score';
const newCfName = 'total product items';
const newExpression = 'COUNT';
const newToFieldModelDisplayName = 'order_items';
const newToFieldColumnDisplayName = 'OrderId';
await modelingHelper.addCalculatedField(page, {
calculatedFieldName,
expression,
modelDisplayName,
toFieldModelDisplayName,
toFieldColumnDisplayName,
});
// update calculated field
await page
.getByTestId(`diagram__model-node__${modelDisplayName}`)
.getByRole('button', { name: 'more' })
.nth(1)
.click();
await page.getByText('Edit').click();
await page.getByLabel('Name').click();
await page.getByLabel('Name').fill(newCfName);
await page.getByTestId('common__descriptive-select').click();
await page.getByTitle(newExpression).locator('div').click();
await page
.getByTestId('common__lineage-field-block')
.filter({ hasText: modelDisplayName })
.getByText(toFieldModelDisplayName, { exact: true })
.click();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: newToFieldModelDisplayName })
.scrollIntoViewIfNeeded();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: newToFieldModelDisplayName })
.click();
await expect(
page
.getByTestId('common__lineage-field-block')
.getByText(newToFieldModelDisplayName, { exact: true }),
).toHaveCount(2);
await expect(page.getByText('Please select a field.')).toBeVisible();
await page.getByTestId('common__lineage-fields-select').last().click();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: newToFieldColumnDisplayName })
.scrollIntoViewIfNeeded();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: newToFieldColumnDisplayName })
.click();
await page.getByRole('button', { name: 'Save' }).click();
await expect(
page.getByText('Successfully updated calculated field.'),
).toBeVisible();
// delete calculated field
await modelingHelper.deleteCalculatedField(page, modelDisplayName);
});
});
+9 -92
View File
@@ -132,90 +132,20 @@ test.describe('Test NBA sample dataset', () => {
await expect(page).toHaveURL('/modeling', { timeout: 60000 });
const modelDisplayName = 'game';
const cfName = 'count of games';
const calculatedFieldName = 'count of games';
const expression = 'Sum';
const toFieldModelDisplayName = 'line_score';
const toFieldColumnDisplayName = 'GameId';
const newToFieldModelDisplayName = 'player_games';
const newToFieldColumnDisplayName = 'GameID';
await page
.getByRole('complementary')
.getByText(modelDisplayName, { exact: true })
.click();
// add calculated field
await page
.getByTestId(`diagram__model-node__${modelDisplayName}`)
.locator('div')
.filter({ hasText: /^Calculated Fields$/ })
.getByRole('button')
.first()
.click();
await expect(page.locator('.ant-modal-mask')).toBeVisible();
await expect(page.locator('div.ant-modal')).toBeVisible();
await expect(
page
.locator('div.ant-modal-title')
.filter({ hasText: 'Add calculated field' }),
).toBeVisible();
await expect(
page
.getByLabel('Add calculated field')
.getByLabel('Close', { exact: true }),
).toBeVisible();
await page.getByLabel('Name').click();
await page.getByLabel('Name').fill(cfName);
await page.getByTestId('common__descriptive-select').click();
await page.getByTitle(expression).locator('div').click();
await expect(page.getByTestId('common__lineage')).toBeVisible();
await expect(
page
.getByTestId('common__lineage-field-block')
.getByText(modelDisplayName, { exact: true }),
).toBeVisible();
await page.getByTestId('common__lineage-fields-select').click();
// for skip disabled item
await page.getByTestId('common__lineage-fields-select').press('ArrowDown');
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldModelDisplayName })
.scrollIntoViewIfNeeded();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldModelDisplayName })
.click();
await expect(
page
.getByTestId('common__lineage-field-block')
.getByText(toFieldModelDisplayName, { exact: true }),
).toHaveCount(2);
await expect(page.getByText('Please select a field.')).toBeVisible();
await page.getByTestId('common__lineage-fields-select').last().click();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldColumnDisplayName })
.scrollIntoViewIfNeeded();
await page
.getByTestId('common__fields__select-option')
.filter({ hasText: toFieldColumnDisplayName })
.click();
await page.getByRole('button', { name: 'Save' }).click();
await expect(
page.getByText('Successfully created calculated field.'),
).toBeVisible();
await modelingHelper.addCalculatedField(page, {
calculatedFieldName,
expression,
modelDisplayName,
toFieldModelDisplayName,
toFieldColumnDisplayName,
});
// update calculated field
await page
@@ -279,19 +209,6 @@ test.describe('Test NBA sample dataset', () => {
).toBeVisible();
// delete calculated field
await page
.getByRole('complementary')
.getByText(modelDisplayName, { exact: true })
.click();
await page
.getByTestId(`diagram__model-node__${modelDisplayName}`)
.getByRole('button', { name: 'more' })
.nth(1)
.click();
await page.getByText('Delete', { exact: true }).click();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(
page.getByText('Successfully deleted calculated field.'),
).toBeVisible();
await modelingHelper.deleteCalculatedField(page, modelDisplayName);
});
});
+3 -3
View File
@@ -21,7 +21,7 @@ export default defineConfig({
use: {
// Base URL to use in actions like `await page.goto('/')`.
baseURL: 'http://127.0.0.1:3333',
baseURL: 'http://127.0.0.1:3000',
// Collect trace when retrying the failed test.
trace: 'on-first-retry',
@@ -45,8 +45,8 @@ export default defineConfig({
],
// Run your local dev server before starting the tests.
webServer: {
command: 'NODE_ENV=test yarn start -p 3333',
url: 'http://127.0.0.1:3333',
command: 'NODE_ENV=test yarn start -p 3000',
url: 'http://127.0.0.1:3000',
reuseExistingServer: true,
},
});
@@ -113,7 +113,12 @@ export default function ModelTree(props: Props) {
},
{
key: 'add-model',
icon: () => <PlusSquareOutlined onClick={() => onOpenModelDrawer()} />,
icon: () => (
<PlusSquareOutlined
data-testid="add-model"
onClick={() => onOpenModelDrawer()}
/>
),
},
],
});