diff --git a/.github/workflows/ci-e2e.yaml b/.github/workflows/ci-e2e.yaml
index 464c8a2e5..4b5bf5603 100644
--- a/.github/workflows/ci-e2e.yaml
+++ b/.github/workflows/ci-e2e.yaml
@@ -69,11 +69,12 @@ jobs:
- name: Build twenty-shared
run: npx nx build twenty-shared
+ - name: Install Playwright Browsers
+ run: npx nx setup twenty-e2e-testing
+
- name: Setup environment files
run: |
- cp packages/twenty-e2e-testing/.env.example packages/twenty-e2e-testing/.env
cp packages/twenty-front/.env.example packages/twenty-front/.env
- cp packages/twenty-e2e-testing/.env.example packages/twenty-e2e-testing/.env
npx nx reset:env twenty-server
- name: Build frontend
@@ -87,7 +88,7 @@ jobs:
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:reset
-
+
- name: Start server
run: |
npx nx start twenty-server &
@@ -105,9 +106,6 @@ jobs:
npx nx run twenty-server:worker:ci &
echo "Worker started"
- - name: Install Playwright Browsers
- run: npx nx setup twenty-e2e-testing
-
- name: Run Playwright tests
run: npx nx test twenty-e2e-testing
diff --git a/packages/twenty-e2e-testing/.env.example b/packages/twenty-e2e-testing/.env.example
index c00e8e0d4..9f0b80f99 100644
--- a/packages/twenty-e2e-testing/.env.example
+++ b/packages/twenty-e2e-testing/.env.example
@@ -1,5 +1,6 @@
# Note that provide always without trailing forward slash to have expected behaviour
FRONTEND_BASE_URL=http://localhost:3001
+BACKEND_BASE_URL=http://localhost:3000
DEFAULT_LOGIN=tim@apple.dev
DEFAULT_PASSWORD=Applecar2025
WEBSITE_URL=https://twenty.com
diff --git a/packages/twenty-e2e-testing/config/customreporter.ts b/packages/twenty-e2e-testing/config/customreporter.ts
deleted file mode 100644
index 62a602ef8..000000000
--- a/packages/twenty-e2e-testing/config/customreporter.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- Reporter,
- FullConfig,
- Suite,
- TestCase,
- TestResult,
- FullResult,
-} from '@playwright/test/reporter';
-
-class CustomReporter implements Reporter {
- constructor(options: { customOption?: string } = {}) {
- console.log(
- `my-awesome-reporter setup with customOption set to ${options.customOption}`,
- );
- }
-
- onBegin(config: FullConfig, suite: Suite) {
- console.log(`Starting the run with ${suite.allTests().length} tests`);
- }
-
- onTestBegin(test: TestCase) {
- console.log(`Starting test ${test.title}`);
- }
-
- onTestEnd(test: TestCase, result: TestResult) {
- console.log(`Finished test ${test.title}: ${result.status}`);
- }
-
- onEnd(result: FullResult) {
- console.log(`Finished the run: ${result.status}`);
- }
-}
-export default CustomReporter;
diff --git a/packages/twenty-e2e-testing/drivers/env_variables.ts b/packages/twenty-e2e-testing/drivers/env_variables.ts
deleted file mode 100644
index 768b1872c..000000000
--- a/packages/twenty-e2e-testing/drivers/env_variables.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as fs from 'fs';
-import path from 'path';
-
-export const envVariables = (variables: string) => {
- let payload = `
- PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/default
- ACCESS_TOKEN_SECRET=replace_me_with_a_random_string_access
- LOGIN_TOKEN_SECRET=replace_me_with_a_random_string_login
- REFRESH_TOKEN_SECRET=replace_me_with_a_random_string_refresh
- FILE_TOKEN_SECRET=replace_me_with_a_random_string_refresh
- REDIS_URL=redis://localhost:6379
- `;
- payload = payload.concat(variables);
- fs.writeFile(
- path.join(__dirname, '..', '..', 'twenty-server', '.env'),
- payload,
- (err) => {
- throw err;
- },
- );
-};
diff --git a/packages/twenty-e2e-testing/drivers/shell_driver.ts b/packages/twenty-e2e-testing/drivers/shell_driver.ts
deleted file mode 100644
index cf293c032..000000000
--- a/packages/twenty-e2e-testing/drivers/shell_driver.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { exec } from 'child_process';
-
-export async function sh(cmd) {
- return new Promise((resolve, reject) => {
- exec(cmd, (err, stdout, stderr) => {
- if (err) {
- reject(err);
- } else {
- resolve({ stdout, stderr });
- }
- });
- });
-}
diff --git a/packages/twenty-e2e-testing/lib/fixtures/blank-workflow.ts b/packages/twenty-e2e-testing/lib/fixtures/blank-workflow.ts
new file mode 100644
index 000000000..c6cdf62cf
--- /dev/null
+++ b/packages/twenty-e2e-testing/lib/fixtures/blank-workflow.ts
@@ -0,0 +1,69 @@
+import { test as base, expect, Page } from '@playwright/test';
+import { randomUUID } from 'node:crypto';
+import { createWorkflow } from '../requests/create-workflow';
+import { deleteWorkflow } from '../requests/delete-workflow';
+import { destroyWorkflow } from '../requests/destroy-workflow';
+
+export class WorkflowVisualizerPage {
+ #page: Page;
+
+ workflowId: string;
+ workflowName: string;
+
+ constructor({ page, workflowName }: { page: Page; workflowName: string }) {
+ this.#page = page;
+ this.workflowName = workflowName;
+ }
+
+ async createOneWorkflow() {
+ const id = randomUUID();
+
+ const response = await createWorkflow({
+ page: this.#page,
+ workflowId: id,
+ workflowName: this.workflowName,
+ });
+
+ expect(response.status()).toBe(200);
+
+ const responseBody = await response.json();
+ expect(responseBody.data.createWorkflow.id).toBe(id);
+
+ this.workflowId = id;
+ }
+
+ async goToWorkflowVisualizerPage() {
+ await this.#page.goto(`/object/workflow/${this.workflowId}`);
+
+ const workflowName = this.#page.getByRole('button', {
+ name: this.workflowName,
+ });
+
+ await expect(workflowName).toBeVisible();
+ }
+}
+
+export const test = base.extend<{ workflowVisualizer: WorkflowVisualizerPage }>(
+ {
+ workflowVisualizer: async ({ page }, use) => {
+ const workflowVisualizer = new WorkflowVisualizerPage({
+ page,
+ workflowName: 'Test Workflow',
+ });
+
+ await workflowVisualizer.createOneWorkflow();
+ await workflowVisualizer.goToWorkflowVisualizerPage();
+
+ await use(workflowVisualizer);
+
+ await deleteWorkflow({
+ page,
+ workflowId: workflowVisualizer.workflowId,
+ });
+ await destroyWorkflow({
+ page,
+ workflowId: workflowVisualizer.workflowId,
+ });
+ },
+ },
+);
diff --git a/packages/twenty-e2e-testing/lib/pom/loginPage.ts b/packages/twenty-e2e-testing/lib/pom/loginPage.ts
index 15aa1ca22..6f69a683f 100644
--- a/packages/twenty-e2e-testing/lib/pom/loginPage.ts
+++ b/packages/twenty-e2e-testing/lib/pom/loginPage.ts
@@ -1,4 +1,4 @@
-import { Locator, Page } from '@playwright/test';
+import { expect, Locator, Page } from '@playwright/test';
export class LoginPage {
private readonly loginWithGoogleButton: Locator;
@@ -98,6 +98,8 @@ export class LoginPage {
}
async typeEmail(email: string) {
+ await expect(this.emailField).toBeVisible();
+
await this.emailField.fill(email);
}
diff --git a/packages/twenty-e2e-testing/lib/requests/backend.ts b/packages/twenty-e2e-testing/lib/requests/backend.ts
new file mode 100644
index 000000000..73d57b1a5
--- /dev/null
+++ b/packages/twenty-e2e-testing/lib/requests/backend.ts
@@ -0,0 +1,4 @@
+export const backendGraphQLUrl = new URL(
+ '/graphql',
+ process.env.BACKEND_BASE_URL,
+).toString();
diff --git a/packages/twenty-e2e-testing/lib/requests/create-workflow.ts b/packages/twenty-e2e-testing/lib/requests/create-workflow.ts
new file mode 100644
index 000000000..33aa22783
--- /dev/null
+++ b/packages/twenty-e2e-testing/lib/requests/create-workflow.ts
@@ -0,0 +1,32 @@
+import { Page } from '@playwright/test';
+import { getAuthToken } from '../utils/getAuthToken';
+import { backendGraphQLUrl } from './backend';
+
+export const createWorkflow = async ({
+ page,
+ workflowId,
+ workflowName,
+}: {
+ page: Page;
+ workflowId: string;
+ workflowName: string;
+}) => {
+ const { authToken } = await getAuthToken(page);
+
+ return page.request.post(backendGraphQLUrl, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ data: {
+ operationName: 'CreateOneWorkflow',
+ query:
+ 'mutation CreateOneWorkflow($input: WorkflowCreateInput!) { createWorkflow(data: $input) { __typename id } }',
+ variables: {
+ input: {
+ id: workflowId,
+ name: workflowName,
+ },
+ },
+ },
+ });
+};
diff --git a/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts b/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts
new file mode 100644
index 000000000..299768d67
--- /dev/null
+++ b/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts
@@ -0,0 +1,25 @@
+import { Page } from '@playwright/test';
+import { getAuthToken } from '../utils/getAuthToken';
+import { backendGraphQLUrl } from './backend';
+
+export const deleteWorkflow = async ({
+ page,
+ workflowId,
+}: {
+ page: Page;
+ workflowId: string;
+}) => {
+ const { authToken } = await getAuthToken(page);
+
+ return page.request.post(backendGraphQLUrl, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ data: {
+ operationName: 'DeleteOneWorkflow',
+ variables: { idToDelete: workflowId },
+ query:
+ 'mutation DeleteOneWorkflow($idToDelete: ID!) {\n deleteWorkflow(id: $idToDelete) {\n __typename\n deletedAt\n id\n }\n}',
+ },
+ });
+};
diff --git a/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts b/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts
new file mode 100644
index 000000000..2191a3e3d
--- /dev/null
+++ b/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts
@@ -0,0 +1,25 @@
+import { Page } from '@playwright/test';
+import { getAuthToken } from '../utils/getAuthToken';
+import { backendGraphQLUrl } from './backend';
+
+export const destroyWorkflow = async ({
+ page,
+ workflowId,
+}: {
+ page: Page;
+ workflowId: string;
+}) => {
+ const { authToken } = await getAuthToken(page);
+
+ return page.request.post(backendGraphQLUrl, {
+ headers: {
+ Authorization: `Bearer ${authToken}`,
+ },
+ data: {
+ operationName: 'DestroyOneWorkflow',
+ variables: { idToDestroy: workflowId },
+ query:
+ 'mutation DestroyOneWorkflow($idToDestroy: ID!) {\n destroyWorkflow(id: $idToDestroy) {\n id\n __typename\n }\n}',
+ },
+ });
+};
diff --git a/packages/twenty-e2e-testing/lib/utils/getAuthToken.ts b/packages/twenty-e2e-testing/lib/utils/getAuthToken.ts
new file mode 100644
index 000000000..38534fdf2
--- /dev/null
+++ b/packages/twenty-e2e-testing/lib/utils/getAuthToken.ts
@@ -0,0 +1,15 @@
+import { Page } from '@playwright/test';
+
+export const getAuthToken = async (page: Page) => {
+ const storageState = await page.context().storageState();
+ const authCookie = storageState.cookies.find(
+ (cookie) => cookie.name === 'tokenPair',
+ );
+ if (!authCookie) {
+ throw new Error('No auth cookie found');
+ }
+ const token = JSON.parse(decodeURIComponent(authCookie.value)).accessToken
+ .token;
+
+ return { authToken: token };
+};
diff --git a/packages/twenty-e2e-testing/playwright.config.ts b/packages/twenty-e2e-testing/playwright.config.ts
index 448c9f8d8..6fea9a702 100644
--- a/packages/twenty-e2e-testing/playwright.config.ts
+++ b/packages/twenty-e2e-testing/playwright.config.ts
@@ -30,10 +30,6 @@ export default defineConfig({
screenshot: 'on', // either 'on' here or in different method in modules, if 'on' all screenshots are overwritten each time the test is run
headless: true, // instead of changing it to false, run 'yarn test:e2e:debug' or 'yarn test:e2e:ui'
testIdAttribute: 'data-testid', // taken from Twenty source
- viewport: { width: 1920, height: 1080 }, // most laptops use this resolution
- launchOptions: {
- slowMo: 500, // time in milliseconds between each step, better to use it than explicitly define timeout in tests
- },
},
expect: {
timeout: 5000,
@@ -41,24 +37,17 @@ export default defineConfig({
reporter: process.env.CI ? 'github' : 'list',
projects: [
{
- name: 'Login setup',
- testMatch: /login\.setup\.ts/, // finds all tests matching this regex, in this case only 1 test should be found
+ name: 'setup',
+ testMatch: /.*\.setup\.ts/,
},
{
- name: 'Demo check',
+ name: 'chrome',
use: {
...devices['Desktop Chrome'],
- },
- testMatch: /demo\/demo_basic\.e2e-spec\.ts/,
- },
- {
- name: 'Authentication',
- use: {
permissions: ['clipboard-read', 'clipboard-write'],
storageState: path.resolve(__dirname, '.auth', 'user.json'), // takes saved cookies from directory
},
- dependencies: ['Login setup'],
- testMatch: /authentication\/.+\.e2e-spec\.ts/, // forces to run login setup before running tests from this project - CASE SENSITIVE
+ dependencies: ['setup'],
},
//{
diff --git a/packages/twenty-e2e-testing/project.json b/packages/twenty-e2e-testing/project.json
index 9dd38917f..e820fd172 100644
--- a/packages/twenty-e2e-testing/project.json
+++ b/packages/twenty-e2e-testing/project.json
@@ -8,7 +8,8 @@
"options": {
"cwd": "packages/twenty-e2e-testing",
"commands": [
- "yarn playwright install"
+ "yarn playwright install",
+ "cp .env.example .env"
]
}
},
diff --git a/packages/twenty-e2e-testing/tests/authentication/signup_invite_email.e2e-spec.ts b/packages/twenty-e2e-testing/tests/authentication/signup_invite_email.e2e-spec.ts
deleted file mode 100644
index fc7f3537d..000000000
--- a/packages/twenty-e2e-testing/tests/authentication/signup_invite_email.e2e-spec.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { expect, test } from './fixture';
-
-test.describe('Authentication test', () => {
- const email = 'test@apple.dev';
- const firstName = 'John';
- const lastName = 'Doe';
- let testCompleted = false;
-
- test('Sign up with invite link via email', async ({
- page,
- loginPage,
- leftMenu,
- membersSection,
- settingsPage,
- }) => {
- const inviteLink: string =
- await test.step('Go to Settings and copy invite link', async () => {
- await page.goto(process.env.LINK); // skip login page (and redirect) when running on environments with multi-workspace enabled
- await leftMenu.goToSettings();
- await settingsPage.goToMembersSection();
- await membersSection.copyInviteLink();
- return await page.evaluate('navigator.clipboard.readText()');
- });
- await test.step('Go to invite link', async () => {
- await settingsPage.logout();
- await page.goto(inviteLink);
- });
- await test.step('Create new account', async () => {
- await loginPage.clickLoginWithEmail();
- await loginPage.typeEmail(email);
- await loginPage.clickContinueButton();
- await loginPage.typePassword(process.env.DEFAULT_PASSWORD);
- await loginPage.clickSignUpButton();
- await loginPage.typeFirstName(firstName);
- await loginPage.typeLastName(lastName);
- await loginPage.clickContinueButton();
- await loginPage.noSyncWithGoogle();
- testCompleted = true;
- });
- });
-
- test.afterEach(
- async ({
- page,
- confirmationModal,
- leftMenu,
- profileSection,
- settingsPage,
- }) => {
- if (testCompleted) {
- // security measurement to clean up only after test is completed,
- // otherwise default account used for tests may be deleted and resetting database will be necessary
- await test.step('Cleanup - deleting account', async () => {
- await leftMenu.goToSettings();
- await settingsPage.goToProfileSection();
- await profileSection.deleteAccount();
- await confirmationModal.typePlaceholderToInput();
- await confirmationModal.clickConfirmButton();
- expect(page.url()).toContain('/welcome');
- });
- }
- },
- );
-});
diff --git a/packages/twenty-e2e-testing/tests/authentication/signup_invite_email.spec.ts b/packages/twenty-e2e-testing/tests/authentication/signup_invite_email.spec.ts
new file mode 100644
index 000000000..f36ba1094
--- /dev/null
+++ b/packages/twenty-e2e-testing/tests/authentication/signup_invite_email.spec.ts
@@ -0,0 +1,60 @@
+import { randomUUID } from 'crypto';
+import { expect, test } from './fixture';
+
+test('Sign up with invite link via email', async ({
+ page,
+ loginPage,
+ leftMenu,
+ membersSection,
+ settingsPage,
+ profileSection,
+ confirmationModal,
+}) => {
+ const email = `test${randomUUID().replaceAll('-', '')}@apple.dev`;
+ const firstName = 'John';
+ const lastName = 'Doe';
+
+ const inviteLink: string =
+ await test.step('Go to Settings and copy invite link', async () => {
+ await page.goto(process.env.LINK); // skip login page (and redirect) when running on environments with multi-workspace enabled
+ await leftMenu.goToSettings();
+ await settingsPage.goToMembersSection();
+ await membersSection.copyInviteLink();
+ return await page.evaluate('navigator.clipboard.readText()');
+ });
+
+ await test.step('Go to invite link', async () => {
+ await settingsPage.logout();
+
+ await Promise.all([
+ expect(page.getByText(/Join .+ team/)).toBeVisible(),
+
+ page.goto(inviteLink),
+ ]);
+ });
+
+ await test.step('Create new account', async () => {
+ await loginPage.clickLoginWithEmail();
+ await loginPage.typeEmail(email);
+ await loginPage.clickContinueButton();
+ await loginPage.typePassword(process.env.DEFAULT_PASSWORD);
+ await loginPage.clickSignUpButton();
+ await loginPage.typeFirstName(firstName);
+ await loginPage.typeLastName(lastName);
+ await loginPage.clickContinueButton();
+ await loginPage.noSyncWithGoogle();
+ });
+
+ await test.step('Delete account from workspace', async () => {
+ await leftMenu.goToSettings();
+ await settingsPage.goToProfileSection();
+ await profileSection.deleteAccount();
+ await confirmationModal.typePlaceholderToInput();
+
+ await Promise.all([
+ page.waitForURL('/welcome'),
+
+ confirmationModal.clickConfirmButton(),
+ ]);
+ });
+});
diff --git a/packages/twenty-e2e-testing/tests/demo/demo_basic.e2e-spec.ts b/packages/twenty-e2e-testing/tests/demo/demo_basic.spec.ts
similarity index 100%
rename from packages/twenty-e2e-testing/tests/demo/demo_basic.e2e-spec.ts
rename to packages/twenty-e2e-testing/tests/demo/demo_basic.spec.ts
diff --git a/packages/twenty-e2e-testing/tests/login.setup.ts b/packages/twenty-e2e-testing/tests/login.setup.ts
index 3d68ccbcb..bb730f9b9 100644
--- a/packages/twenty-e2e-testing/tests/login.setup.ts
+++ b/packages/twenty-e2e-testing/tests/login.setup.ts
@@ -1,6 +1,6 @@
-import { expect, test as base } from '@playwright/test';
-import { LoginPage } from '../lib/pom/loginPage';
+import { test as base, expect } from '@playwright/test';
import path from 'path';
+import { LoginPage } from '../lib/pom/loginPage';
// fixture
const test = base.extend<{ loginPage: LoginPage }>({
@@ -29,7 +29,7 @@ test('Login test', async ({ loginPage, page }) => {
await loginPage.typePassword(process.env.DEFAULT_PASSWORD);
await page.waitForLoadState('networkidle');
await loginPage.clickSignInButton();
- await expect(page.getByText('Welcome to Twenty')).not.toBeVisible();
+ await expect(page.getByText(/Welcome to .+/)).not.toBeVisible();
},
);
diff --git a/packages/twenty-e2e-testing/tests/workflow-creation.spec.ts b/packages/twenty-e2e-testing/tests/workflow-creation.spec.ts
new file mode 100644
index 000000000..026dd5b7b
--- /dev/null
+++ b/packages/twenty-e2e-testing/tests/workflow-creation.spec.ts
@@ -0,0 +1,67 @@
+import { expect, test } from '@playwright/test';
+import { deleteWorkflow } from '../lib/requests/delete-workflow';
+import { destroyWorkflow } from '../lib/requests/destroy-workflow';
+
+test('Create workflow', async ({ page }) => {
+ const NEW_WORKFLOW_NAME = 'Test Workflow';
+
+ await page.goto('/');
+
+ const workflowsLink = page.getByRole('link', { name: 'Workflows' });
+ await workflowsLink.click();
+
+ const createWorkflowButton = page.getByRole('button', { name: 'New record' });
+
+ const [createWorkflowResponse] = await Promise.all([
+ page.waitForResponse(async (response) => {
+ if (!response.url().endsWith('/graphql')) {
+ return false;
+ }
+
+ const requestBody = response.request().postDataJSON();
+
+ return requestBody.operationName === 'CreateOneWorkflow';
+ }),
+
+ await createWorkflowButton.click(),
+ ]);
+
+ const nameInputClosedState = page.getByText('Name').first();
+ await nameInputClosedState.click();
+
+ const nameInput = page.getByRole('textbox');
+ await nameInput.fill(NEW_WORKFLOW_NAME);
+ await nameInput.press('Enter');
+
+ const body = await createWorkflowResponse.json();
+ const newWorkflowId = body.data.createWorkflow.id;
+
+ try {
+ const newWorkflowRowEntryName = page
+ .getByTestId(`row-id-${newWorkflowId}`)
+ .locator('div')
+ .filter({ hasText: NEW_WORKFLOW_NAME })
+ .nth(2);
+
+ await Promise.all([
+ page.waitForURL(
+ (url) => url.pathname === `/object/workflow/${newWorkflowId}`,
+ ),
+
+ newWorkflowRowEntryName.click(),
+ ]);
+
+ const workflowName = page.getByRole('button', { name: NEW_WORKFLOW_NAME });
+
+ await expect(workflowName).toBeVisible();
+ } finally {
+ await deleteWorkflow({
+ page,
+ workflowId: newWorkflowId,
+ });
+ await destroyWorkflow({
+ page,
+ workflowId: newWorkflowId,
+ });
+ }
+});
diff --git a/packages/twenty-e2e-testing/tests/workflow-visualizer.spec.ts b/packages/twenty-e2e-testing/tests/workflow-visualizer.spec.ts
new file mode 100644
index 000000000..8c9b9f62e
--- /dev/null
+++ b/packages/twenty-e2e-testing/tests/workflow-visualizer.spec.ts
@@ -0,0 +1,35 @@
+import { expect } from '@playwright/test';
+import { test } from '../lib/fixtures/blank-workflow';
+
+test('Create simple workflow', async ({ workflowVisualizer, page }) => {
+ const addTriggerButton = page.getByText('Add a Trigger');
+ await addTriggerButton.click();
+
+ const triggerOption = page.getByText('Database Event');
+ await triggerOption.click();
+
+ await expect(
+ page.getByTestId('command-menu').getByRole('textbox'),
+ ).toHaveValue('When a Company is Created');
+
+ const triggerNode = page.getByTestId('rf__node-trigger');
+ await expect(triggerNode).toHaveClass(/selected/);
+ await expect(triggerNode).toHaveText(/Company is Created/);
+
+ const addStepButton = page.getByLabel('Add a step');
+ await addStepButton.click();
+
+ const createRecordOption = page.getByText('Create Record');
+
+ await createRecordOption.click();
+
+ await expect(
+ page.getByTestId('command-menu').getByRole('textbox').first(),
+ ).toHaveValue('Create Record');
+
+ const createRecordNode = page
+ .locator('.react-flow__node.selected')
+ .getByText('Create Record');
+ await expect(createRecordNode).toBeVisible();
+ await expect(triggerNode).not.toHaveClass(/selected/);
+});
diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
index ddffd3fe8..b3124e701 100644
--- a/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
+++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenuContainer.tsx
@@ -91,6 +91,7 @@ export const CommandMenuContainer = ({
{isCommandMenuOpened && (
{
-
+
);
};