Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cypress/snapshots/**/__diff_output__/
.claude
cypress/screenshots
cypress/videos
cypress/downloads
.serena

*storybook.log
Expand Down
2 changes: 0 additions & 2 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ export default defineConfig({

// Force disable fullscreen
launchOptions.args.push('--force-device-scale-factor=1');

console.log('Chrome launch args:', launchOptions.args);
}

return launchOptions;
Expand Down
79 changes: 40 additions & 39 deletions cypress/e2e/account/avatar/avatar-api.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { avatarTestUtils } from './avatar-test-utils';
import { AccountSelectors, AvatarUiSelectors } from '../../../support/selectors';
import { testLog } from '../../../support/test-helpers';

const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils;
const { updateUserMetadata, AuthTestUtils, AvatarSelectors, WorkspaceSelectors } = imports;
Expand All @@ -14,41 +16,41 @@ describe('Avatar API', () => {
const authUtils = new AuthTestUtils();
const testAvatarUrl = 'https://api.dicebear.com/7.x/avataaars/svg?seed=test';

cy.task('log', 'Step 1: Visit login page');
testLog.info( 'Step 1: Visit login page');
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

cy.task('log', 'Step 2: Sign in with test account');
testLog.info( 'Step 2: Sign in with test account');
authUtils.signInWithTestUrl(testEmail);

cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

cy.task('log', 'Step 3: Update avatar via API');
testLog.info( 'Step 3: Update avatar via API');
updateUserMetadata(testAvatarUrl).then((response) => {
cy.task('log', `API Response: ${JSON.stringify(response)}`);
testLog.info( `API Response: ${JSON.stringify(response)}`);
expect(response.status).to.equal(200);
});

cy.task('log', 'Step 4: Reload page to see updated avatar');
testLog.info( 'Step 4: Reload page to see updated avatar');
cy.reload();
cy.wait(3000);

cy.task('log', 'Step 5: Open Account Settings to verify avatar');
testLog.info( 'Step 5: Open Account Settings to verify avatar');
WorkspaceSelectors.dropdownTrigger().click();
cy.wait(1000);
cy.get('[data-testid="account-settings-button"]').click();
AccountSelectors.settingsButton().click();
AvatarSelectors.accountSettingsDialog().should('be.visible');

cy.task('log', 'Step 6: Verify avatar image is displayed in Account Settings');
testLog.info( 'Step 6: Verify avatar image is displayed in Account Settings');
// Note: Account Settings dialog may not display avatar directly
// The avatar is displayed via getUserIconUrl which prioritizes workspace member avatar
// Since we updated user metadata (icon_url), it should be available
// But the actual display location might be in the workspace dropdown or elsewhere

// Wait for any avatar image to be present and loaded
// The AvatarImage component loads asynchronously and sets opacity to 0 while loading
cy.get('[data-testid="avatar-image"]', { timeout: 10000 })
AvatarUiSelectors.image()
.should('exist')
.should(($imgs) => {
// Find the first visible avatar image (opacity not 0)
Expand All @@ -66,7 +68,7 @@ describe('Avatar API', () => {
});

// Verify that the avatar image has loaded (check for non-empty src and visible state)
cy.get('[data-testid="avatar-image"]').then(($imgs) => {
AvatarUiSelectors.image().then(($imgs) => {
let foundLoaded = false;
$imgs.each((index, img) => {
const $img = Cypress.$(img);
Expand All @@ -75,7 +77,7 @@ describe('Avatar API', () => {

if (opacity > 0 && src.length > 0) {
foundLoaded = true;
cy.task('log', `Found loaded avatar image with src: ${src.substring(0, 50)}...`);
testLog.info( `Found loaded avatar image with src: ${src.substring(0, 50)}...`);
return false; // break
}
});
Expand All @@ -88,44 +90,44 @@ describe('Avatar API', () => {
const authUtils = new AuthTestUtils();
const testAvatarUrl = 'https://api.dicebear.com/7.x/avataaars/svg?seed=test';

cy.task('log', '========== Step 1: Visit login page ==========');
testLog.info( '========== Step 1: Visit login page ==========');
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

cy.task('log', '========== Step 2: Sign in with test account ==========');
testLog.info( '========== Step 2: Sign in with test account ==========');
authUtils.signInWithTestUrl(testEmail);
cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

cy.task('log', '========== Step 3: Get token from localStorage ==========');
testLog.info( '========== Step 3: Get token from localStorage ==========');
cy.window()
.its('localStorage')
.invoke('getItem', 'token')
.then((tokenStr) => {
cy.task('log', `Token string: ${tokenStr ? 'Found' : 'Not found'}`);
testLog.info( `Token string: ${tokenStr ? 'Found' : 'Not found'}`);
const token = JSON.parse(tokenStr);
const accessToken = token.access_token;
cy.task('log', `Access token: ${accessToken ? 'Present (length: ' + accessToken.length + ')' : 'Missing'}`);
testLog.info( `Access token: ${accessToken ? 'Present (length: ' + accessToken.length + ')' : 'Missing'}`);
});

cy.task('log', '========== Step 4: Making API request ==========');
cy.task('log', `URL: ${avatarTestUtils.APPFLOWY_BASE_URL}/api/user/update`);
cy.task('log', `Avatar URL: ${testAvatarUrl}`);
testLog.info( '========== Step 4: Making API request ==========');
testLog.info( `URL: ${avatarTestUtils.APPFLOWY_BASE_URL}/api/user/update`);
testLog.info( `Avatar URL: ${testAvatarUrl}`);

updateUserMetadata(testAvatarUrl).then((response) => {
cy.task('log', '========== Step 5: Checking response ==========');
cy.task('log', `Response is null: ${response === null}`);
cy.task('log', `Response type: ${typeof response}`);
cy.task('log', `Response status: ${response?.status}`);
cy.task('log', `Response body: ${JSON.stringify(response?.body)}`);
cy.task('log', `Response headers: ${JSON.stringify(response?.headers)}`);
testLog.info( '========== Step 5: Checking response ==========');
testLog.info( `Response is null: ${response === null}`);
testLog.info( `Response type: ${typeof response}`);
testLog.info( `Response status: ${response?.status}`);
testLog.info( `Response body: ${JSON.stringify(response?.body)}`);
testLog.info( `Response headers: ${JSON.stringify(response?.headers)}`);

expect(response).to.not.be.null;
expect(response.status).to.equal(200);

if (response.body) {
cy.task('log', `Response body code: ${response.body.code}`);
cy.task('log', `Response body message: ${response.body.message}`);
testLog.info( `Response body code: ${response.body.code}`);
testLog.info( `Response body message: ${response.body.message}`);
}
});
});
Expand All @@ -135,59 +137,58 @@ describe('Avatar API', () => {
const authUtils = new AuthTestUtils();
const testEmoji = '🎨';

cy.task('log', 'Step 1: Visit login page');
testLog.info( 'Step 1: Visit login page');
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

cy.task('log', 'Step 2: Sign in with test account');
testLog.info( 'Step 2: Sign in with test account');
authUtils.signInWithTestUrl(testEmail);

cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

cy.task('log', 'Step 3: Update avatar to emoji via API');
testLog.info( 'Step 3: Update avatar to emoji via API');
updateUserMetadata(testEmoji).then((response) => {
expect(response).to.not.be.null;
expect(response.status).to.equal(200);
});

cy.task('log', 'Step 4: Reload page');
testLog.info( 'Step 4: Reload page');
cy.reload();
cy.wait(3000);

cy.task('log', 'Step 5: Open Account Settings');
testLog.info( 'Step 5: Open Account Settings');
WorkspaceSelectors.dropdownTrigger().click();
cy.wait(1000);
cy.get('[data-testid="account-settings-button"]').click();
AccountSelectors.settingsButton().click();
AvatarSelectors.accountSettingsDialog().should('be.visible');

cy.task('log', 'Step 6: Verify emoji is displayed in fallback');
testLog.info( 'Step 6: Verify emoji is displayed in fallback');
AvatarSelectors.avatarFallback().should('contain.text', testEmoji);
});

it('should display fallback character when no avatar is set', () => {
const testEmail = generateRandomEmail();
const authUtils = new AuthTestUtils();

cy.task('log', 'Step 1: Visit login page');
testLog.info( 'Step 1: Visit login page');
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

cy.task('log', 'Step 2: Sign in with test account (no avatar set)');
testLog.info( 'Step 2: Sign in with test account (no avatar set)');
authUtils.signInWithTestUrl(testEmail).then(() => {
cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

cy.task('log', 'Step 3: Open workspace dropdown to see avatar');
testLog.info( 'Step 3: Open workspace dropdown to see avatar');
WorkspaceSelectors.dropdownTrigger().click();
cy.wait(500);

cy.task('log', 'Step 4: Verify fallback is displayed in workspace dropdown avatar');
testLog.info( 'Step 4: Verify fallback is displayed in workspace dropdown avatar');
AvatarSelectors.workspaceDropdownAvatar().within(() => {
AvatarSelectors.avatarFallback().should('be.visible');
});
});
});
});
});

9 changes: 5 additions & 4 deletions cypress/e2e/account/avatar/avatar-database.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { avatarTestUtils } from './avatar-test-utils';
import { testLog } from '../../../support/test-helpers';

const { generateRandomEmail, setupBeforeEach, imports } = avatarTestUtils;
const { updateWorkspaceMemberAvatar, AuthTestUtils, dbUtils } = imports;
Expand All @@ -14,16 +15,16 @@ describe('Avatar Database', () => {
const authUtils = new AuthTestUtils();
const testAvatarUrl = 'https://api.dicebear.com/7.x/avataaars/svg?seed=db-test';

cy.task('log', 'Step 1: Visit login page');
testLog.info( 'Step 1: Visit login page');
cy.visit('/login', { failOnStatusCode: false });
cy.wait(2000);

cy.task('log', 'Step 2: Sign in with test account');
testLog.info( 'Step 2: Sign in with test account');
authUtils.signInWithTestUrl(testEmail).then(() => {
cy.url({ timeout: 30000 }).should('include', '/app');
cy.wait(3000);

cy.task('log', 'Step 3: Set avatar via API');
testLog.info( 'Step 3: Set avatar via API');
dbUtils.getCurrentWorkspaceId().then((workspaceId) => {
expect(workspaceId).to.not.be.null;

Expand All @@ -33,7 +34,7 @@ describe('Avatar Database', () => {

cy.wait(3000);

cy.task('log', 'Step 4: Verify avatar is stored in database');
testLog.info( 'Step 4: Verify avatar is stored in database');
dbUtils.getCurrentUserUuid().then((userUuid) => {
expect(userUuid).to.not.be.null;

Expand Down
Loading