Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1b9e2d3
wip
ijreilly Oct 29, 2025
69394a8
Merge branch 'main' of github.com:twentyhq/twenty into fix--fix-e2e
ijreilly Nov 5, 2025
2c8316b
fix workflow-creation
ijreilly Nov 5, 2025
296ac89
wip
ijreilly Nov 18, 2025
5082be3
Merge branch 'main' of github.com:twentyhq/twenty into fix--fix-e2e
ijreilly Dec 9, 2025
d977378
fix workflow creation test
ijreilly Dec 9, 2025
2732510
wip
ijreilly Dec 9, 2025
5946f06
wip
ijreilly Dec 10, 2025
97c2714
create-kanban-view test
ijreilly Dec 10, 2025
59fa5f4
fix tests
ijreilly Dec 12, 2025
dbe7b95
test
ijreilly Dec 12, 2025
7cac768
reintroduce screenshots
ijreilly Dec 12, 2025
ab6f56d
test - reset authState
ijreilly Dec 15, 2025
f83d7f3
tmp just for tests to be run
ijreilly Dec 15, 2025
e3c130e
add multiworkspace to env
ijreilly Dec 15, 2025
670dd52
add log to debug
ijreilly Dec 15, 2025
418dda9
fix - add variable to .env.example
ijreilly Dec 15, 2025
02196ff
use .env.test in e2e
ijreilly Dec 15, 2025
cb5004e
test to fix
ijreilly Dec 15, 2025
3a0617a
test
ijreilly Dec 15, 2025
0a5e875
fix
ijreilly Dec 15, 2025
7c26b46
Merge branch 'main' into fix--fix-e2e
ijreilly Dec 15, 2025
cf7169b
comment out some tests
ijreilly Dec 16, 2025
01add78
Merge branch 'fix--fix-e2e' of github.com:twentyhq/twenty into fix--f…
ijreilly Dec 16, 2025
f82e1c2
attempt to add tests
ijreilly Dec 16, 2025
46a4a5e
add flaky to logs
ijreilly Dec 16, 2025
b4e8a19
Merge branch 'main' of github.com:twentyhq/twenty into fix--fix-e2e
ijreilly Dec 16, 2025
96913d8
remove usage of process.env.LINK as baseURL
ijreilly Dec 16, 2025
a8cd7ba
improvements
ijreilly Dec 16, 2025
b92aad9
improve
ijreilly Dec 16, 2025
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
6 changes: 3 additions & 3 deletions packages/twenty-e2e-testing/lib/requests/create-workflow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { getAuthToken } from '../utils/getAuthToken';
import { type Page } from '@playwright/test';
import { getAccessAuthToken } from '../utils/getAccessAuthToken';
import { backendGraphQLUrl } from './backend';

export const createWorkflow = async ({
Expand All @@ -11,7 +11,7 @@ export const createWorkflow = async ({
workflowId: string;
workflowName: string;
}) => {
const { authToken } = await getAuthToken(page);
const { authToken } = await getAccessAuthToken(page);

return page.request.post(backendGraphQLUrl, {
headers: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { getAuthToken } from '../utils/getAuthToken';
import { getAuthToken } from '../utils/getAccessAuthToken';
import { backendGraphQLUrl } from './backend';

export const deleteWorkflow = async ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Page } from '@playwright/test';
import { getAuthToken } from '../utils/getAuthToken';
import { getAuthToken } from '../utils/getAccessAuthToken';
import { backendGraphQLUrl } from './backend';

export const destroyWorkflow = async ({
Expand Down
30 changes: 30 additions & 0 deletions packages/twenty-e2e-testing/lib/utils/getAccessAuthToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type Page } from '@playwright/test';

const decodeToken = (cookie: any) =>
JSON.parse(decodeURIComponent(cookie.value)).accessOrWorkspaceAgnosticToken
?.token;

const decodePayload = (jwt: string) =>
JSON.parse(Buffer.from(jwt.split('.')[1], 'base64url').toString());


export const getAccessAuthToken = async (page: Page) => {
const storageState = await page.context().storageState();
const tokenCookies = storageState.cookies.filter(
(cookie) => cookie.name === 'tokenPair',
);
if (!tokenCookies) {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 16, 2025

Choose a reason for hiding this comment

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

P1: Bug: Array.filter() always returns an array (possibly empty), never null or undefined. This check will never throw even when no matching cookies exist. Should check tokenCookies.length === 0 instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-e2e-testing/lib/utils/getAccessAuthToken.ts, line 16:

<comment>Bug: `Array.filter()` always returns an array (possibly empty), never `null` or `undefined`. This check will never throw even when no matching cookies exist. Should check `tokenCookies.length === 0` instead.</comment>

<file context>
@@ -0,0 +1,30 @@
+  const tokenCookies = storageState.cookies.filter(
+    (cookie) =&gt; cookie.name === &#39;tokenPair&#39;,
+  );
+  if (!tokenCookies) {
+    throw new Error(&#39;No auth cookie found&#39;);
+  }
</file context>
Fix with Cubic

throw new Error('No auth cookie found');
}
const accessTokenCookie = tokenCookies.find(
(cookie) => {
const payload = decodePayload(decodeToken(cookie) ?? '');
return payload.type === 'ACCESS';
}
);

const token = JSON.parse(decodeURIComponent(accessTokenCookie?.value ?? '')).accessOrWorkspaceAgnosticToken
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 16, 2025

Choose a reason for hiding this comment

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

P1: Bug: If no access token cookie is found, JSON.parse('') will throw a SyntaxError. Add a check for accessTokenCookie before parsing its value.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-e2e-testing/lib/utils/getAccessAuthToken.ts, line 26:

<comment>Bug: If no access token cookie is found, `JSON.parse(&#39;&#39;)` will throw a `SyntaxError`. Add a check for `accessTokenCookie` before parsing its value.</comment>

<file context>
@@ -0,0 +1,30 @@
+    }
+  );
+
+  const token = JSON.parse(decodeURIComponent(accessTokenCookie?.value ?? &#39;&#39;)).accessOrWorkspaceAgnosticToken
+  .token;
+
</file context>
Fix with Cubic

.token;

return { authToken: token };
};
15 changes: 0 additions & 15 deletions packages/twenty-e2e-testing/lib/utils/getAuthToken.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/twenty-e2e-testing/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default defineConfig({
use: {
baseURL: process.env.FRONTEND_BASE_URL || 'http://localhost:3001',
trace: 'retain-on-failure', // trace takes EVERYTHING from page source, records every single step, should be used only when normal debugging won't work
screenshot: 'on', // either 'on' here or in different method in modules, if 'on' all screenshots are overwritten each time the test is run
screenshot: 'off', // disable automatic screenshots to reduce IO and artifacts size
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
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ test('Sign up with invite link via email', async ({
await leftMenu.goToSettings();
await settingsPage.goToProfileSection();
await profileSection.deleteAccount();
await expect(page.getByText('Account Deletion')).toBeVisible();
await confirmationModal.typePlaceholderToInput();
await confirmationModal.clickConfirmButton();

await Promise.all([
page.waitForURL('/welcome'),

confirmationModal.clickConfirmButton(),
page.waitForURL('**/welcome'),
]);
});
});
59 changes: 59 additions & 0 deletions packages/twenty-e2e-testing/tests/create-kanban-view.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { expect, test } from '../lib/fixtures/screenshot';

if (process.env.LINK) {
const baseURL = new URL(process.env.LINK).origin;
test.use({ baseURL });
}
test.describe.serial('Create Kanban View', () => {
test('Create Industry Select Field', async ({ page }) => {
await page.getByRole('link', { name: 'Settings' }).click();
await page.getByRole('link', { name: 'Data model' }).click();
await page.getByRole('link', { name: 'Opportunities' }).click();
await page.getByRole('button', { name: 'Add Field' }).click();
await page.getByRole('link', { name: 'Select', exact: true }).click();
await page.getByRole('textbox', { name: 'Employees' }).click();
await page.getByRole('textbox', { name: 'Employees' }).fill('Industry');
await page.getByRole('textbox').nth(1).click();
await page.getByRole('textbox').nth(1).press('ControlOrMeta+a');
await page.getByRole('textbox').nth(1).fill('Food');
await page.getByRole('button', { name: 'Add option' }).click();
await page.getByRole('button', { name: 'Option 2' }).getByRole('textbox').fill('Tech');
await page.getByRole('button', { name: 'Add option' }).click();
await page.getByRole('button', { name: 'Option 3' }).getByRole('textbox').fill('Travel');
await page.getByRole('button', { name: 'Save' }).click();
await page.waitForURL('**/objects/opportunities');
await page.waitForSelector('text=Industry');
await expect(page.getByText('Industry')).toBeVisible();
});

test('Create Kanban View from Industry Select Field', async ({ page }) => {
await page.getByRole('link', { name: 'Opportunities' }).click();
await page.getByRole('button', { name: 'All Opportunities ·' }).click();
await page.getByText('Add view').click();
await page.getByRole('textbox').press('ControlOrMeta+a');
await page.getByRole('textbox').fill('By industry');
await page.getByRole('button', { name: 'Table', exact: true }).click();
await page.getByText('Kanban').click();
await page.locator('[aria-controls="view-picker-kanban-field-options"]').click();
await page.getByRole('option', { name: 'Industry' }).click();
// Use exact: true to ensure we only click the button with the label "Create"
await page.getByRole('button', { name: 'Create new view' }).click();
await expect(page.getByText('Food')).toBeVisible();
await expect(page.getByText('Tech')).toBeVisible();
await expect(page.getByText('Travel')).toBeVisible();
await expect(page.getByText('No value')).toBeVisible();
const byIndustryElements = await page.locator('text=By industry').all();
expect(byIndustryElements.length).toBeGreaterThanOrEqual(1);
for (const element of byIndustryElements) {
await expect(element).toBeVisible();
}
await page.getByText('Options').click();
await page.getByText('Group', { exact: true }).click();
await Promise.all([page.getByRole('button', { name: 'Hide group null', exact: true }).click(),
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 16, 2025

Choose a reason for hiding this comment

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

P2: Race condition: In Promise.all, the click action is listed before waitForRequest, which may cause the request to be missed if it fires before the listener is set up. Following the pattern in workflow-creation.spec.ts, the wait should come first. Also consider using waitForResponse instead of waitForRequest for consistency with existing tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-e2e-testing/tests/create-kanban-view.spec.ts, line 52:

<comment>Race condition: In `Promise.all`, the click action is listed before `waitForRequest`, which may cause the request to be missed if it fires before the listener is set up. Following the pattern in `workflow-creation.spec.ts`, the wait should come first. Also consider using `waitForResponse` instead of `waitForRequest` for consistency with existing tests.</comment>

<file context>
@@ -0,0 +1,59 @@
+  }
+  await page.getByText(&#39;Options&#39;).click();
+  await page.getByText(&#39;Group&#39;, { exact: true }).click();
+  await Promise.all([page.getByRole(&#39;button&#39;, { name: &#39;Hide group null&#39;, exact: true }).click(),
+    page.waitForRequest((req) =&gt; {
+    return req.url().includes(&#39;/metadata&#39;) &amp;&amp;
</file context>
Fix with Cubic

page.waitForRequest((req) => {
return req.url().includes('/metadata') &&
req.method() === 'POST'
})]);
Comment on lines +48 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The problem with this kind of assertion is that we can make dozens of calls to /metadata in a single navigation. This assertion proves nothing.

We can leave it as is, but since it doesn’t prove that the action we expect was performed, it could lead to flakiness.

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.

I don't think we expect other POST requests in this setup, nor that for now we actually do any /metadata post requests while navigating only

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.

But I intent to make sure this is not flaky so i m keeping that in mind

await expect(page.getByText('No value')).not.toBeVisible();
});
})
163 changes: 163 additions & 0 deletions packages/twenty-e2e-testing/tests/create-record.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { expect, test } from '../lib/fixtures/screenshot';
import { backendGraphQLUrl } from '../lib/requests/backend';
import { getAccessAuthToken } from '../lib/utils/getAccessAuthToken';

if (process.env.LINK) {
const baseURL = new URL(process.env.LINK).origin;
test.use({ baseURL });
}

const query = `query FindOnePerson($objectRecordId: UUID!) {
person(
filter: {or: [{deletedAt: {is: NULL}}, {deletedAt: {is: NOT_NULL}}], id: {eq: $objectRecordId}}
) {
company {
name
}
emails {
primaryEmail
additionalEmails
__typename
}
id
intro
jobTitle
linkedinLink {
primaryLinkUrl
primaryLinkLabel
secondaryLinks
__typename
}
name {
firstName
lastName
__typename
}
performanceRating
phones {
primaryPhoneNumber
primaryPhoneCountryCode
primaryPhoneCallingCode
additionalPhones
__typename
}
position
workPreference
updatedAt
}
}`

test('Create and update record', async ({ page }) => {
await page.getByRole('link', { name: 'People' }).click();
await page.getByRole('button', { name: 'Create new record' }).click();

// Generate a random email for testing
const randomEmail = `testuser_${Math.random().toString(36).substring(2, 10)}@example.com`;
// Fill first name and last name
const firstNameInput = page.getByRole('textbox', { name: 'F‌‌irst name' })
await expect(firstNameInput).toBeFocused();
await firstNameInput.fill('John');
const lastNameInput = page.getByPlaceholder('L‌‌ast name');
await expect(lastNameInput).toBeVisible();
await lastNameInput.fill('Doe');
await lastNameInput.press('Enter');

// Focus on recordFieldList
const recordFieldList = page.locator('div[aria-label="Record fields list"]');
await expect(recordFieldList).toBeVisible();
await recordFieldList.getByText('Emails').first().click();

// Fill email
const emailInput = recordFieldList.getByText('Emails').nth(1);
await expect(emailInput).toBeVisible();
await emailInput.click({ force: true });
await page.getByPlaceholder('Email').fill(randomEmail);
await page.keyboard.press('Enter');
await page.keyboard.press('Escape');
await recordFieldList.getByText('Emails').first().click();


// Fill intro
const introInput = recordFieldList.getByText('Intro').nth(1);
await expect(introInput).toBeVisible();
await introInput.click({ force: true });
await introInput.click({ force: true });
await page.getByPlaceholder('Intro').fill('This is an intro');
await page.getByPlaceholder('Intro').press('Enter');

// Fill URL
const urlInput = recordFieldList.getByText('Linkedin').nth(1);
await expect(urlInput).toBeVisible();
await urlInput.click({ force: true });
await page.getByPlaceholder('URL').fill('linkedin.com/johndoe');
await page.getByPlaceholder('URL').press('Enter');

// Click on 4th star to rate
recordFieldList.getByText('Performance Rating').first().click({ force: true });
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 16, 2025

Choose a reason for hiding this comment

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

P1: Missing await on Playwright click action. This can cause race conditions and flaky tests since the test won't wait for the click to complete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-e2e-testing/tests/create-record.spec.ts, line 96:

<comment>Missing `await` on Playwright click action. This can cause race conditions and flaky tests since the test won&#39;t wait for the click to complete.</comment>

<file context>
@@ -0,0 +1,163 @@
+    await page.getByPlaceholder(&#39;URL&#39;).press(&#39;Enter&#39;);
+
+    // Click on 4th star to rate
+    recordFieldList.getByText(&#39;Performance Rating&#39;).first().click({ force: true });
+    const ratingContainer = recordFieldList.locator(&#39;div[aria-label=&quot;Rating&quot;]&#39;);
+    await ratingContainer.locator(&#39;svg&#39;).nth(3).click({force: true});
</file context>
Fix with Cubic

const ratingContainer = recordFieldList.locator('div[aria-label="Rating"]');
await ratingContainer.locator('svg').nth(3).click({force: true});

// Fill phone field
const phoneInput = recordFieldList.getByText('Phones').nth(1);
await expect(phoneInput).toBeVisible();
await phoneInput.click({ force: true });
await page.getByPlaceholder('Phone').fill('+336 1 122 3344');
await page.getByPlaceholder('Phone').press('Enter');

// Fill work preference
await recordFieldList.getByText('Work Preference').first().click({force: true});
await recordFieldList.getByText('Work Preference').nth(1).click({force: true});
const options = page.getByRole('listbox');
await options.getByText('Hybrid').first().click({force: true});
recordFieldList.getByText('Work Preference').first().click({force: true});
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 16, 2025

Choose a reason for hiding this comment

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

P1: Missing await on Playwright click action. This can cause race conditions and flaky tests since the test won't wait for the click to complete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-e2e-testing/tests/create-record.spec.ts, line 112:

<comment>Missing `await` on Playwright click action. This can cause race conditions and flaky tests since the test won&#39;t wait for the click to complete.</comment>

<file context>
@@ -0,0 +1,163 @@
+    await recordFieldList.getByText(&#39;Work Preference&#39;).nth(1).click({force: true});
+    const options = page.getByRole(&#39;listbox&#39;);
+    await options.getByText(&#39;Hybrid&#39;).first().click({force: true});
+    recordFieldList.getByText(&#39;Work Preference&#39;).first().click({force: true});
+
+    // Fill company relation
</file context>
Fix with Cubic


// Fill company relation
const companyRelationHeader = page.getByLabel('Company (relation)');
await expect(companyRelationHeader).toBeVisible();

await companyRelationHeader.locator('.tabler-icon-pencil').click();
await page.getByRole('textbox', { name: 'Search' }).fill('Goog');
await expect(page.getByRole('option', { name: 'Google' })).toBeVisible();
const [updatePersonResponse] = await Promise.all([
page.waitForResponse(async (response) => {
if (!response.url().endsWith('/graphql')) {
return false;
}

const requestBody = response.request().postDataJSON();

return requestBody.operationName === 'UpdateOnePerson';
}),
await page.getByRole('option', { name: 'Google' }).click({force: true})
]);

const body = await updatePersonResponse.json()
const newPersonId = body.data.updatePerson.id;

// Check data was saved
const { authToken } = await getAccessAuthToken(page);
const findOnePersonResponse = await page.request.post(backendGraphQLUrl, {
headers: {
Authorization: `Bearer ${authToken}`,
},
data: {
operationName: 'FindOnePerson',
query,
variables: {
objectRecordId: newPersonId,
}
},
});

const findOnePersonReponseBody = await findOnePersonResponse.json();

expect(findOnePersonReponseBody.data.person.name.firstName).toBe('John');
expect(findOnePersonReponseBody.data.person.name.lastName).toBe('Doe');
expect(findOnePersonReponseBody.data.person.emails.primaryEmail).toBe(randomEmail);
expect(findOnePersonReponseBody.data.person.intro).toBe('This is an intro');
expect(findOnePersonReponseBody.data.person.linkedinLink.primaryLinkUrl).toBe('linkedin.com/johndoe');
expect(findOnePersonReponseBody.data.person.phones.primaryPhoneNumber).toBe('611223344');
expect(findOnePersonReponseBody.data.person.workPreference).toEqual(['HYBRID']);
expect(findOnePersonReponseBody.data.person.company.name).toBe('Google');

});
6 changes: 5 additions & 1 deletion packages/twenty-e2e-testing/tests/login.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@
await page.waitForLoadState('networkidle');
await loginPage.clickSignInButton();
await expect(page.getByText(/Welcome to .+/)).not.toBeVisible();
await expect(page.getByText('Choose a workspace')).toBeVisible();

Check failure on line 33 in packages/twenty-e2e-testing/tests/login.setup.ts

View workflow job for this annotation

GitHub Actions / test

[setup] › tests/login.setup.ts:13:1 › Login test

1) [setup] › tests/login.setup.ts:13:1 › Login test › Logging in http://localhost:3001/ as tim@apple.dev Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByText('Choose a workspace') Expected: visible Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 5000ms - waiting for getByText('Choose a workspace') 31 | await loginPage.clickSignInButton(); 32 | await expect(page.getByText(/Welcome to .+/)).not.toBeVisible(); > 33 | await expect(page.getByText('Choose a workspace')).toBeVisible(); | ^ 34 | await page.getByText('Apple', {exact: true}).click(); 35 | await page.waitForFunction(() => window.location.href.includes('verify')); 36 | await page.waitForFunction(() => !window.location.href.includes('verify')); at /home/runner/work/twenty/twenty/packages/twenty-e2e-testing/tests/login.setup.ts:33:58 at /home/runner/work/twenty/twenty/packages/twenty-e2e-testing/tests/login.setup.ts:17:3

Check failure on line 33 in packages/twenty-e2e-testing/tests/login.setup.ts

View workflow job for this annotation

GitHub Actions / test

[setup] › tests/login.setup.ts:13:1 › Login test

1) [setup] › tests/login.setup.ts:13:1 › Login test › Logging in http://localhost:3001/ as tim@apple.dev Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByText('Choose a workspace') Expected: visible Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 5000ms - waiting for getByText('Choose a workspace') 31 | await loginPage.clickSignInButton(); 32 | await expect(page.getByText(/Welcome to .+/)).not.toBeVisible(); > 33 | await expect(page.getByText('Choose a workspace')).toBeVisible(); | ^ 34 | await page.getByText('Apple', {exact: true}).click(); 35 | await page.waitForFunction(() => window.location.href.includes('verify')); 36 | await page.waitForFunction(() => !window.location.href.includes('verify')); at /home/runner/work/twenty/twenty/packages/twenty-e2e-testing/tests/login.setup.ts:33:58 at /home/runner/work/twenty/twenty/packages/twenty-e2e-testing/tests/login.setup.ts:17:3

Check failure on line 33 in packages/twenty-e2e-testing/tests/login.setup.ts

View workflow job for this annotation

GitHub Actions / test

[setup] › tests/login.setup.ts:13:1 › Login test

1) [setup] › tests/login.setup.ts:13:1 › Login test › Logging in http://localhost:3001/ as tim@apple.dev Error: expect(locator).toBeVisible() failed Locator: getByText('Choose a workspace') Expected: visible Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 5000ms - waiting for getByText('Choose a workspace') 31 | await loginPage.clickSignInButton(); 32 | await expect(page.getByText(/Welcome to .+/)).not.toBeVisible(); > 33 | await expect(page.getByText('Choose a workspace')).toBeVisible(); | ^ 34 | await page.getByText('Apple', {exact: true}).click(); 35 | await page.waitForFunction(() => window.location.href.includes('verify')); 36 | await page.waitForFunction(() => !window.location.href.includes('verify')); at /home/runner/work/twenty/twenty/packages/twenty-e2e-testing/tests/login.setup.ts:33:58 at /home/runner/work/twenty/twenty/packages/twenty-e2e-testing/tests/login.setup.ts:17:3
await page.getByText('Apple', {exact: true}).click();
await page.waitForFunction(() => window.location.href.includes('verify'));
await page.waitForFunction(() => !window.location.href.includes('verify'));
process.env.LINK = page.url();
},
);

await test.step('Saved auth state', async () => {
await page.context().storageState({
path: path.resolve(__dirname, '..', '.auth', 'user.json'),
});
process.env.LINK = page.url();
});
});
10 changes: 7 additions & 3 deletions packages/twenty-e2e-testing/tests/workflow-creation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { expect, test } from '@playwright/test';
import { expect, test } from '../lib/fixtures/screenshot';
import { deleteWorkflow } from '../lib/requests/delete-workflow';
import { destroyWorkflow } from '../lib/requests/destroy-workflow';

if (process.env.LINK) {
const baseURL = new URL(process.env.LINK).origin;
test.use({ baseURL });
}
test('Create workflow', async ({ page }) => {
const NEW_WORKFLOW_NAME = 'Test Workflow';

await page.goto('/');
await page.goto(process.env.LINK);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 16, 2025

Choose a reason for hiding this comment

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

P1: page.goto(process.env.LINK) will fail if LINK environment variable is not set, as it will pass undefined. The original page.goto('/') worked with the default baseURL. Consider providing a fallback.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-e2e-testing/tests/workflow-creation.spec.ts, line 12:

<comment>`page.goto(process.env.LINK)` will fail if `LINK` environment variable is not set, as it will pass `undefined`. The original `page.goto(&#39;/&#39;)` worked with the default baseURL. Consider providing a fallback.</comment>

<file context>
@@ -1,11 +1,15 @@
   const NEW_WORKFLOW_NAME = &#39;Test Workflow&#39;;
 
-  await page.goto(&#39;/&#39;);
+  await page.goto(process.env.LINK);
 
   const workflowsLink = page.getByRole(&#39;link&#39;, { name: &#39;Workflows&#39; });
</file context>
Suggested change
await page.goto(process.env.LINK);
await page.goto(process.env.LINK ?? '/');

✅ Addressed in 96913d8


const workflowsLink = page.getByRole('link', { name: 'Workflows' });
await workflowsLink.click();
Expand All @@ -25,7 +29,7 @@ test('Create workflow', async ({ page }) => {
return requestBody.operationName === 'CreateOneWorkflow';
}),

createWorkflowButton.click(),
createWorkflowButton.click()
]);

const recordName = page.getByTestId('top-bar-title').getByText('Untitled');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { SignInUpMode } from '@/auth/types/signInUpMode';
import { getAvailableWorkspacePathAndSearchParams } from '@/auth/utils/availableWorkspacesUtils';
import { isRequestingCaptchaTokenState } from '@/captcha/states/isRequestingCaptchaTokenState';
import { useCaptcha } from '@/client-config/hooks/useCaptcha';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
import { isDefined } from 'twenty-shared/utils';
Expand Down Expand Up @@ -155,6 +156,7 @@ export const SignInUpGlobalScopeForm = () => {
const isRequestingCaptchaToken = useRecoilValue(
isRequestingCaptchaTokenState,
);
const { isCaptchaReady } = useCaptcha();

const [showErrors, setShowErrors] = useState(false);

Expand Down Expand Up @@ -284,7 +286,9 @@ export const SignInUpGlobalScopeForm = () => {
)}
<MainButton
disabled={
isRequestingCaptchaToken || form.formState.isSubmitting
isRequestingCaptchaToken ||
form.formState.isSubmitting ||
(signInUpStep !== SignInUpStep.Password && !isCaptchaReady)
}
title={
signInUpStep === SignInUpStep.Password
Expand Down
Loading
Loading