0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

test: add ui tests for social connectors (#4328)

This commit is contained in:
Xiao Yijun 2023-08-15 15:33:50 +08:00 committed by GitHub
parent 9862aacc8d
commit 96634b06b7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 602 additions and 1 deletions

View file

@ -71,7 +71,7 @@ export const convertResponseToForm = (connector: ConnectorResponse): ConnectorFo
logo,
logoDark,
target: conditional(
type === ConnectorType.Social && !isStandard && (metadata.target ?? target)
type === ConnectorType.Social && (isStandard ? target : metadata.target ?? target)
),
syncProfile: syncProfile ? SyncProfileMode.EachSignIn : SyncProfileMode.OnlyAtRegister,
jsonConfig: JSON.stringify(config, null, 2),

View file

@ -0,0 +1,370 @@
/* eslint-disable max-lines */
export type SocialConnectorCase = {
groupFactoryId?: string;
factoryId: string;
name: string;
initialFormData: Record<string, string>;
updateFormData: Record<string, string>;
errorFormData: Record<string, string>;
standardBasicFormData?: Record<string, string>;
};
const google: SocialConnectorCase = {
factoryId: 'google-universal',
name: 'Google',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
'formConfig.scope': '',
},
};
const apple: SocialConnectorCase = {
factoryId: 'apple-universal',
name: 'Apple',
initialFormData: {
'formConfig.clientId': 'client-id',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
},
errorFormData: {
'formConfig.clientId': '',
},
};
const facebook: SocialConnectorCase = {
factoryId: 'facebook-universal',
name: 'Facebook',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
'formConfig.scope': '',
},
};
const github: SocialConnectorCase = {
factoryId: 'github-universal',
name: 'GitHub',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
'formConfig.scope': '',
},
};
const discord: SocialConnectorCase = {
factoryId: 'discord-universal',
name: 'Discord',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
'formConfig.scope': '',
},
};
const kakao: SocialConnectorCase = {
factoryId: 'kakao-universal',
name: 'Kakao',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
},
};
const naver: SocialConnectorCase = {
factoryId: 'naver-universal',
name: 'Naver',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
},
};
const microsoft: SocialConnectorCase = {
factoryId: 'azuread-universal',
name: 'Microsoft',
initialFormData: {
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret',
'formConfig.cloudInstance': 'cloud-instance',
'formConfig.tenantId': 'tenant-id',
},
updateFormData: {
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret',
'formConfig.cloudInstance': 'new-cloud-instance',
'formConfig.tenantId': 'new-tenant-id',
},
errorFormData: {
'formConfig.clientId': '',
'formConfig.clientSecret': '',
'formConfig.cloudInstance': '',
'formConfig.tenantId': '',
},
};
const feishu: SocialConnectorCase = {
factoryId: 'feishu-web',
name: 'Feishu',
initialFormData: {
'formConfig.appId': 'app-id',
'formConfig.appSecret': 'app-secret',
},
updateFormData: {
'formConfig.appId': 'new-app-id',
'formConfig.appSecret': 'new-app-secret',
},
errorFormData: {
'formConfig.appId': '',
'formConfig.appSecret': '',
},
};
const wechatNative: SocialConnectorCase = {
groupFactoryId: 'wechat-native',
factoryId: 'wechat-native',
name: 'WeChat',
initialFormData: {
'formConfig.appId': 'app-id',
'formConfig.appSecret': 'app-secret',
'formConfig.universalLinks': 'universal-links',
},
updateFormData: {
'formConfig.appId': 'new-app-id',
'formConfig.appSecret': 'new-app-secret',
'formConfig.universalLinks': 'new-universal-links',
},
errorFormData: {
'formConfig.appId': '',
'formConfig.appSecret': '',
'formConfig.universalLinks': '',
},
};
const wechatWeb: SocialConnectorCase = {
groupFactoryId: 'wechat-native',
factoryId: 'wechat-web',
name: 'WeChat',
initialFormData: {
'formConfig.appId': 'app-id',
'formConfig.appSecret': 'app-secret',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.appId': 'new-app-id',
'formConfig.appSecret': 'new-app-secret',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.appId': '',
'formConfig.appSecret': '',
'formConfig.scope': '',
},
};
const alipayNative: SocialConnectorCase = {
groupFactoryId: 'alipay-native',
factoryId: 'alipay-native',
name: 'Alipay',
initialFormData: {
'formConfig.appId': 'app-id',
'formConfig.privateKey': 'private-key',
},
updateFormData: {
'formConfig.appId': 'new-app-id',
'formConfig.privateKey': 'new-private-key',
},
errorFormData: {
'formConfig.appId': '',
'formConfig.privateKey': '',
},
};
const alipayWeb: SocialConnectorCase = {
groupFactoryId: 'alipay-native',
factoryId: 'alipay-web',
name: 'Alipay',
initialFormData: {
'formConfig.appId': 'app-id',
'formConfig.privateKey': 'private-key',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.appId': 'new-app-id',
'formConfig.privateKey': 'new-private-key',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.appId': '',
'formConfig.privateKey': '',
'formConfig.scope': '',
},
};
const oauth2: SocialConnectorCase = {
factoryId: 'oauth2',
name: 'OAuth 2.0',
initialFormData: {
'formConfig.authorizationEndpoint': 'authorization-endpoint',
'formConfig.tokenEndpoint': 'token-endpoint',
'formConfig.userInfoEndpoint': 'user-info-endpoint',
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret-id',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.authorizationEndpoint': 'new-authorization-endpoint',
'formConfig.tokenEndpoint': 'new-token-endpoint',
'formConfig.userInfoEndpoint': 'new-user-info-endpoint',
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret-id',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.authorizationEndpoint': '',
'formConfig.tokenEndpoint': '',
'formConfig.userInfoEndpoint': '',
'formConfig.clientId': '',
'formConfig.clientSecret': '',
'formConfig.scope': '',
},
standardBasicFormData: {
name: 'OAuth 2.0',
target: 'oauth2',
},
};
const oidc: SocialConnectorCase = {
factoryId: 'oidc',
name: 'OIDC',
initialFormData: {
'formConfig.authorizationEndpoint': 'authorization-endpoint',
'formConfig.tokenEndpoint': 'token-endpoint',
'formConfig.clientId': 'client-id',
'formConfig.clientSecret': 'client-secret-id',
'formConfig.scope': 'scope',
},
updateFormData: {
'formConfig.authorizationEndpoint': 'new-authorization-endpoint',
'formConfig.tokenEndpoint': 'new-token-endpoint',
'formConfig.clientId': 'new-client-id',
'formConfig.clientSecret': 'new-client-secret-id',
'formConfig.scope': 'new-scope',
},
errorFormData: {
'formConfig.authorizationEndpoint': '',
'formConfig.tokenEndpoint': '',
'formConfig.clientId': '',
'formConfig.clientSecret': '',
},
standardBasicFormData: {
name: 'OIDC',
target: 'oidc',
},
};
const saml: SocialConnectorCase = {
factoryId: 'saml',
name: 'SAML',
initialFormData: {
'formConfig.entityID': 'entity-id',
'formConfig.signInEndpoint': 'sign-in-endpoint',
'formConfig.x509Certificate': 'x509-certificate',
'formConfig.idpMetadataXml': 'idp-metadata-xml',
'formConfig.assertionConsumerServiceUrl': 'assertion-consumer-service-url',
},
updateFormData: {
'formConfig.entityID': 'new-entity-id',
'formConfig.signInEndpoint': 'new-sign-in-endpoint',
'formConfig.x509Certificate': 'new-x509-certificate',
'formConfig.idpMetadataXml': 'new-idp-metadata-xml',
'formConfig.assertionConsumerServiceUrl': 'new-assertion-consumer-service-url',
},
errorFormData: {
'formConfig.entityID': '',
'formConfig.signInEndpoint': '',
'formConfig.x509Certificate': '',
'formConfig.idpMetadataXml': '',
'formConfig.assertionConsumerServiceUrl': '',
},
standardBasicFormData: {
name: 'SAML',
target: 'saml',
},
};
export const socialConnectorTestCases = [
google,
apple,
facebook,
github,
discord,
kakao,
naver,
microsoft,
feishu,
wechatNative,
wechatWeb,
alipayNative,
alipayWeb,
oauth2,
oidc,
saml,
];
/* eslint-enable max-lines */

View file

@ -0,0 +1,199 @@
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
import {
expectUnsavedChangesAlert,
goToAdminConsole,
trySaveChanges,
waitForSuccessToast,
} from '#src/ui-helpers/index.js';
import { expectNavigation, appendPathname } from '#src/utils.js';
import {
socialConnectorTestCases,
type SocialConnectorCase,
} from './social-connector-test-cases.js';
await page.setViewport({ width: 1920, height: 1080 });
describe('social connectors', () => {
const logtoConsoleUrl = new URL(logtoConsoleUrlString);
beforeAll(async () => {
await goToAdminConsole();
});
it('navigate to social connector page', async () => {
await expectNavigation(
page.goto(appendPathname('/console/connectors/social', logtoConsoleUrl).href)
);
await expect(page).toMatchElement(
'div[class$=main] div[class$=headline] div[class$=titleEllipsis]',
{
text: 'Connectors',
}
);
await expect(page).toMatchElement('nav div[class$=item] div[class$=selected] a', {
text: 'Social connectors',
});
expect(page.url()).toBe(new URL(`console/connectors/social`, logtoConsoleUrl).href);
});
it('can open create connector modal from table placeholder', async () => {
await expect(page).toClick('table div[class$=placeholder] button span', {
text: 'Add Social Connector',
});
await expect(page).toMatchElement(
'.ReactModalPortal div[class$=header] div[class$=titleEllipsis]',
{
text: 'Add Social Connector',
}
);
// Close modal
await page.keyboard.press('Escape');
});
it.each(socialConnectorTestCases)(
'can create and modify a(n) $factoryId social connector',
async ({
groupFactoryId,
factoryId,
name,
initialFormData,
updateFormData,
errorFormData,
standardBasicFormData,
}: SocialConnectorCase) => {
await expect(page).toClick('div[class$=headline] button[class$=withIcon] span', {
text: 'Add Social Connector',
});
await expect(page).toMatchElement(
'.ReactModalPortal div[class$=header] div[class$=titleEllipsis]',
{
text: 'Add Social Connector',
}
);
if (groupFactoryId) {
// Platform selector
await page.click(
`.ReactModalPortal div[role=radio]:has(input[name=group][value=${groupFactoryId}])`
);
await page.waitForSelector(
'.ReactModalPortal div[class$=platforms] div[class$=radioGroup]'
);
await page.click(
`.ReactModalPortal div[class$=platforms] div[role=radio]:has(input[name=connector][value=${factoryId}])`
);
} else {
await page.click(
`.ReactModalPortal div[role=radio]:has(input[name=group][value=${factoryId}])`
);
}
await expect(page).toClick('.ReactModalPortal div[class$=footer] button:not(disabled) span', {
text: 'Next',
});
await expect(page).toMatchElement('.ReactModalPortal div[class$=titleEllipsis] span', {
text: name,
});
await expect(page).toMatchElement('.ReactModalPortal div[class$=subtitle] span', {
text: 'A step by step guide to configure your connector',
});
await expect(page).toClick(
'.ReactModalPortal form div[class$=footer] button[type=submit] span',
{
text: 'Save and Done',
}
);
// Display error input
await page.waitForSelector('form div[class$=field] div[class$=error]');
await expect(page).toFillForm('.ReactModalPortal form', {
...standardBasicFormData,
...initialFormData,
});
await expect(page).toClick(
'.ReactModalPortal form div[class$=footer] button[type=submit] span',
{
text: 'Save and Done',
}
);
await waitForSuccessToast(page, 'Saved');
await expect(page).toMatchElement('div[class$=header] div[class$=name] span', {
text: name,
});
// Fill incorrect form
await expect(page).toFillForm('form', errorFormData);
await trySaveChanges(page);
await page.waitForSelector('form div[class$=field] div[class$=error]');
// Update form
await expect(page).toFillForm('form', updateFormData);
await expectUnsavedChangesAlert(page);
await trySaveChanges(page);
await waitForSuccessToast(page, 'Saved');
// Delete connector
await expect(page).toClick(
'div[class$=header] div[class$=operations] button[class$=withIcon]:has(span[class$=icon] > svg[class$=moreIcon])'
);
await expect(page).toMatchElement(
'.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownTitle]',
{
text: 'MORE OPTIONS',
}
);
// Wait for the dropdown menu to be rendered in the correct position
await page.waitForTimeout(500);
await expect(page).toClick(
'.ReactModalPortal div[class$=dropdownContainer] div[role=menuitem]',
{ text: 'Delete' }
);
await page.waitForSelector(
'.ReactModalPortal div[class$=dropdownContainer] div[class$=dropdownTitle]',
{
hidden: true,
}
);
await expect(page).toMatchElement(
'.ReactModalPortal div[class$=header] div[class$=titleEllipsis]',
{
text: 'Reminder',
}
);
await expect(page).toClick('.ReactModalPortal div[class$=footer] button span', {
text: 'Delete',
});
await waitForSuccessToast(page, 'The connector has been successfully deleted');
expect(page.url()).toBe(new URL(`console/connectors/social`, logtoConsoleUrl).href);
}
);
});

View file

@ -1,3 +1,5 @@
import { type Page } from 'puppeteer';
import {
consolePassword,
consoleUsername,
@ -17,3 +19,33 @@ export const goToAdminConsole = async () => {
await expectNavigation(expect(page).toClick('button[name=submit]'));
}
};
export const waitForSuccessToast = async (page: Page, text: string) => {
const successToastHandle = await page.waitForSelector('div[class*=toast][class*=success]');
await expect(successToastHandle).toMatchElement('div[class$=message]', {
text,
});
// Wait the success toast to disappear so that the next time we call this function we will match the brand new toast
await page.waitForSelector('div[class*=toast][class*=success]', {
hidden: true,
});
};
export const expectUnsavedChangesAlert = async (page: Page) => {
// Unsaved changes alert
await page.goBack();
await page.waitForSelector(
'.ReactModalPortal div[class$=content]::-p-text(You have made some changes. Are you sure you want to leave this page?)'
);
await expect(page).toClick('.ReactModalPortal div[class$=footer] button', {
text: 'Stay on Page',
});
};
export const trySaveChanges = async (page: Page) => {
// Wait for the action bar to finish animating
await page.waitForTimeout(500);
await expect(page).toClick('div[class$=actionBar] button span', { text: 'Save Changes' });
};