diff --git a/.changeset/plenty-plums-camp.md b/.changeset/plenty-plums-camp.md
new file mode 100644
index 0000000000..65b973f543
--- /dev/null
+++ b/.changeset/plenty-plums-camp.md
@@ -0,0 +1,5 @@
+---
+'@commercetools-frontend/cypress': minor
+---
+
+Add more options to `loginByOidc` command.
diff --git a/packages/cypress/README.md b/packages/cypress/README.md
index cb46f45556..4b832485b0 100644
--- a/packages/cypress/README.md
+++ b/packages/cypress/README.md
@@ -31,9 +31,15 @@ module.exports = (on, config) => {
Add this line to your project's `cypress/support/commands.js`:
```javascript
-import '@commercetools-frontend/cypress/add-commands'
+import '@commercetools-frontend/cypress/add-commands';
```
### Commands
-* `cy.loginByOidc({ entryPointUriPath })`
+- `cy.loginByOidc({ entryPointUriPath })`
+
+ This command perform the user login using the OIDC workflow to retrieve the session token.
+ The command also requires to load the `custom-application-config.json` (automatically done via the Cypress task) and therefore it may need to load environment variables in case the application config uses environment placeholders.
+ By default, the `.env` and `.env.local` files are attempted to be loaded from the application folder. You can pass a `dotfiles` option to pass a list of names/paths relative to the application folder in case the files in the project have a different name/location.
+
+ > The command also requires the following environment variables to be available: `PROJECT_KEY`, `LOGIN_USER`, `LOGIN_PASSWORD`.
diff --git a/packages/cypress/src/add-commands/index.ts b/packages/cypress/src/add-commands/index.ts
index 6059b2873d..fe2c65332d 100644
--- a/packages/cypress/src/add-commands/index.ts
+++ b/packages/cypress/src/add-commands/index.ts
@@ -9,16 +9,61 @@ declare const Cypress: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
declare const cy: any;
+type CommandLoginByOidcOptions = {
+ /**
+ * The application entry point URI path. This value is used to identify
+ * the correct application config.
+ */
+ entryPointUriPath: string;
+ /**
+ * Pass a list of dotfiles that must be loaded when the custom-application-config.json
+ * is loaded (in case you are using environment placeholder).
+ * By default the following dotfiles are loaded: `.env` and `.env.local`.
+ * You can also define the values using paths relative to the application folder.
+ */
+ dotfiles?: string[];
+ /**
+ * Called before your page has loaded all of its resources.
+ * Use this as a chance to interact for example with the browser storage.
+ */
+ onBeforeLoad?: (win: Window) => void;
+ /**
+ * If defined, visit this route after login.
+ */
+ initialRoute?: string;
+ /**
+ * The project key to access in the user session. The session token is valid for one project key at a time.
+ * Defaults to `Cypress.env('PROJECT_KEY')`.
+ */
+ projectKey?: string;
+ /**
+ * The user login credentials.
+ */
+ login?: {
+ /**
+ * The user email.
+ * Defaults to `Cypress.env('LOGIN_EMAIL') ?? Cypress.env('LOGIN_USER')`.
+ */
+ email: string;
+ /**
+ * The user password.
+ * Defaults to `Cypress.env('LOGIN_PASSWORD')`.
+ */
+ password: string;
+ };
+};
+
Cypress.Commands.add(
'loginByOidc',
- ({ entryPointUriPath }: { entryPointUriPath: string }) => {
+ (commandOptions: CommandLoginByOidcOptions) => {
Cypress.log({ name: 'loginByOidc' });
- const projectKey = Cypress.env('PROJECT_KEY');
+ const projectKey = commandOptions.projectKey ?? Cypress.env('PROJECT_KEY');
const sessionNonce = uuidv4();
cy.task('customApplicationConfig', {
- entryPointUriPath,
+ entryPointUriPath: commandOptions.entryPointUriPath,
+ dotfiles: commandOptions.dotfiles,
}).then((appConfig: ApplicationConfig['env']) => {
const applicationId = appConfig.applicationId;
const sessionScope = buildOidcScope({
@@ -26,14 +71,17 @@ Cypress.Commands.add(
oAuthScopes: appConfig.__DEVELOPMENT__?.oAuthScopes,
teamId: appConfig.__DEVELOPMENT__?.teamId,
});
+ const userCredentials = commandOptions.login ?? {
+ email: Cypress.env('LOGIN_EMAIL') ?? Cypress.env('LOGIN_USER'),
+ password: Cypress.env('LOGIN_PASSWORD'),
+ };
// Perform the login using the API, then store some required values into the browser storage
// and redirect to the auth callback route.
- const options = {
+ const requestOptions = {
method: 'POST',
url: `${appConfig.mcApiUrl}/tokens`,
body: {
- email: Cypress.env('LOGIN_USER'),
- password: Cypress.env('LOGIN_PASSWORD'),
+ ...userCredentials,
client_id: applicationId,
response_type: OIDC_RESPONSE_TYPES.ID_TOKEN,
scope: sessionScope,
@@ -42,24 +90,37 @@ Cypress.Commands.add(
},
followRedirect: false,
};
- cy.request(options).then((res: { body: { redirectTo: string } }) => {
- cy.visit(res.body.redirectTo, {
- onBeforeLoad(win: Window) {
- win.localStorage.setItem(
- STORAGE_KEYS.ACTIVE_PROJECT_KEY,
- projectKey
- );
- win.sessionStorage.setItem(
- `${STORAGE_KEYS.NONCE}_${sessionNonce}`,
- JSON.stringify({ applicationId, query: {} })
- );
- win.sessionStorage.setItem(
- STORAGE_KEYS.SESSION_SCOPE,
- sessionScope
+ cy.request(requestOptions).then(
+ (res: { body: { redirectTo: string } }) => {
+ cy.visit(res.body.redirectTo, {
+ onBeforeLoad(win: Window) {
+ win.localStorage.setItem(
+ STORAGE_KEYS.ACTIVE_PROJECT_KEY,
+ projectKey
+ );
+ win.sessionStorage.setItem(
+ `${STORAGE_KEYS.NONCE}_${sessionNonce}`,
+ JSON.stringify({ applicationId, query: {} })
+ );
+ win.sessionStorage.setItem(
+ STORAGE_KEYS.SESSION_SCOPE,
+ sessionScope
+ );
+
+ if (commandOptions.onBeforeLoad) {
+ commandOptions.onBeforeLoad(win);
+ }
+ },
+ });
+
+ if (commandOptions.initialRoute) {
+ cy.visit(
+ `${Cypress.config('baseUrl')}${commandOptions.initialRoute}`
);
- },
- });
- });
+ cy.url().should('include', commandOptions.initialRoute);
+ }
+ }
+ );
});
}
);
diff --git a/packages/cypress/src/task/index.ts b/packages/cypress/src/task/index.ts
index 6655d35f9e..92c6aaf205 100644
--- a/packages/cypress/src/task/index.ts
+++ b/packages/cypress/src/task/index.ts
@@ -10,14 +10,19 @@ import { processConfig } from '@commercetools-frontend/application-config';
type CustomApplicationConfigTaskOptions = {
entryPointUriPath: string;
+ dotfiles?: string[];
};
type AllCustomApplicationConfigs = Record;
let cachedAllCustomApplicationConfigs: AllCustomApplicationConfigs;
-// TODO: make it configurable?
-const dotfiles = ['.env', '.env.local'];
-const loadEnvironmentVariables = (packageDirPath: string) => {
+const defaultDotfiles = ['.env', '.env.local'];
+
+const loadEnvironmentVariables = (
+ packageDirPath: string,
+ options: CustomApplicationConfigTaskOptions
+) => {
+ const dotfiles = options.dotfiles ?? defaultDotfiles;
return dotfiles.reduce((mergedEnvs, dotfile) => {
const envPath = path.join(packageDirPath, dotfile);
@@ -40,7 +45,9 @@ const loadEnvironmentVariables = (packageDirPath: string) => {
}, process.env);
};
-const loadAllCustomApplicationConfigs = async () => {
+const loadAllCustomApplicationConfigs = async (
+ options: CustomApplicationConfigTaskOptions
+) => {
if (cachedAllCustomApplicationConfigs) {
return cachedAllCustomApplicationConfigs;
}
@@ -62,7 +69,7 @@ const loadAllCustomApplicationConfigs = async () => {
const customAppConfigJson: TJSONSchemaForCustomApplicationConfigurationFiles = JSON.parse(
fs.readFileSync(customAppConfigPath, { encoding: 'utf8' })
);
- const processEnv = loadEnvironmentVariables(packageInfo.dir);
+ const processEnv = loadEnvironmentVariables(packageInfo.dir, options);
const processedConfig = processConfig({
disableCache: true,
configJson: customAppConfigJson,
@@ -79,17 +86,19 @@ const loadAllCustomApplicationConfigs = async () => {
return cachedAllCustomApplicationConfigs;
};
-const customApplicationConfig = async ({
- entryPointUriPath,
-}: CustomApplicationConfigTaskOptions): Promise => {
- const allCustomApplicationConfigs = await loadAllCustomApplicationConfigs();
+const customApplicationConfig = async (
+ options: CustomApplicationConfigTaskOptions
+): Promise => {
+ const allCustomApplicationConfigs = await loadAllCustomApplicationConfigs(
+ options
+ );
const customApplicationConfig =
- allCustomApplicationConfigs[entryPointUriPath];
+ allCustomApplicationConfigs[options.entryPointUriPath];
if (!customApplicationConfig) {
throw new Error(
- `Could not find Custom Application config for entry point "${entryPointUriPath}"`
+ `Could not find Custom Application config for entry point "${options.entryPointUriPath}"`
);
}