Merge branch 'main' into envs
This commit is contained in:
commit
e7a3df4777
39 changed files with 940 additions and 194 deletions
14
.changeset/chilled-kiwis-type.md
Normal file
14
.changeset/chilled-kiwis-type.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
"saleor-app-emails-and-messages": minor
|
||||||
|
"saleor-app-data-importer": minor
|
||||||
|
"saleor-app-products-feed": minor
|
||||||
|
"saleor-app-invoices": minor
|
||||||
|
"saleor-app-klaviyo": minor
|
||||||
|
"saleor-app-search": minor
|
||||||
|
"saleor-app-slack": minor
|
||||||
|
"saleor-app-taxes": minor
|
||||||
|
"saleor-app-cms": minor
|
||||||
|
"saleor-app-crm": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Filled "about" field in App Manifest. Dashboard will display it in app details page now.
|
17
.changeset/three-timers-glow.md
Normal file
17
.changeset/three-timers-glow.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
"saleor-app-emails-and-messages": patch
|
||||||
|
"saleor-app-data-importer": patch
|
||||||
|
"saleor-app-products-feed": patch
|
||||||
|
"saleor-app-monitoring": patch
|
||||||
|
"@saleor/apps-shared": patch
|
||||||
|
"saleor-app-invoices": patch
|
||||||
|
"saleor-app-klaviyo": patch
|
||||||
|
"saleor-app-search": patch
|
||||||
|
"@saleor/apps-ui": patch
|
||||||
|
"saleor-app-slack": patch
|
||||||
|
"saleor-app-taxes": patch
|
||||||
|
"saleor-app-cms": patch
|
||||||
|
"saleor-app-crm": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Update @saleor/app-sdk to 0.41.0
|
|
@ -13,6 +13,8 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about:
|
||||||
|
"CMS App is a multi-integration app that connects Saleor with popular Content Management Systems.",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -11,6 +11,7 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about: "CRM App allows synchronization of customers from Saleor to other platforms",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -9,6 +9,8 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about:
|
||||||
|
"Data Importer allows batch import of shop data to Saleor from sources like CSV or Excel",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -9,6 +9,8 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about:
|
||||||
|
"Emails & Messages App is a multi-vendor Saleor app that integrates with notification services.",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -11,6 +11,8 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about:
|
||||||
|
"An app that generates PDF invoices for Orders and stores them in Saleor file storage.",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -15,6 +15,7 @@ const handler = createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
about: "Klaviyo integration allows sending Klaviyo notifications on Saleor events.",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
"@material-ui/core": "^4.12.4",
|
"@material-ui/core": "^4.12.4",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@material-ui/icons": "^4.11.3",
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
"@material-ui/lab": "4.0.0-alpha.61",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.41.0",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.95",
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about: "Generate feeds consumed by Merchant Platforms",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -15,7 +15,10 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about:
|
||||||
|
"Search App is a multi-integration app that connects your Saleor store with search engines.",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
logo: {
|
logo: {
|
||||||
default: `${apiBaseURL}/logo.png`,
|
default: `${apiBaseURL}/logo.png`,
|
||||||
|
|
|
@ -10,6 +10,8 @@ const handler = createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about:
|
||||||
|
"Saleor Slack integration allows you to get notifications on Slack channel from Saleor events.",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default createManifestHandler({
|
||||||
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
|
||||||
|
|
||||||
const manifest: AppManifest = {
|
const manifest: AppManifest = {
|
||||||
|
about: "Taxes App allows dynamic taxes calculations for orders",
|
||||||
appUrl: iframeBaseUrl,
|
appUrl: iframeBaseUrl,
|
||||||
author: "Saleor Commerce",
|
author: "Saleor Commerce",
|
||||||
brand: {
|
brand: {
|
||||||
|
|
3
packages/e2e/.env.example
Normal file
3
packages/e2e/.env.example
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
INSTANCE_URL=
|
||||||
|
DASHBOARD_USER_EMAIL=
|
||||||
|
DASHBOARD_USER_PASSWORD=
|
4
packages/e2e/.eslintrc
Normal file
4
packages/e2e/.eslintrc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"extends": ["saleor"]
|
||||||
|
}
|
3
packages/e2e/.gitignore
vendored
Normal file
3
packages/e2e/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/playwright/.cache/
|
20
packages/e2e/package.json
Normal file
20
packages/e2e/package.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "@saleor/e2e",
|
||||||
|
"description": "",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "",
|
||||||
|
"scripts": {
|
||||||
|
"e2e": "playwright test",
|
||||||
|
"e2e:ui": "playwright test --ui"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.35.1",
|
||||||
|
"@saleor/app-sdk": "0.40.1",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
|
"eslint-config-saleor": "workspace:*",
|
||||||
|
"zod": "3.20.2"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"main": "index.js"
|
||||||
|
}
|
62
packages/e2e/playwright.config.ts
Normal file
62
packages/e2e/playwright.config.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* Check setup/configuration
|
||||||
|
*/
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests",
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: false,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
// reporter: "html",
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
// baseURL: 'http://127.0.0.1:3000',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: "on-first-retry",
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* name: 'firefox',
|
||||||
|
* use: { ...devices['Desktop Firefox'] },
|
||||||
|
* },
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* name: 'webkit',
|
||||||
|
* use: { ...devices['Desktop Safari'] },
|
||||||
|
* },
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
/*
|
||||||
|
* webServer: {
|
||||||
|
* command: 'npm run start',
|
||||||
|
* url: 'http://127.0.0.1:3000',
|
||||||
|
* reuseExistingServer: !process.env.CI,
|
||||||
|
* },
|
||||||
|
*/
|
||||||
|
});
|
17
packages/e2e/setup/configuration.ts
Normal file
17
packages/e2e/setup/configuration.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const instanceUrl = process.env.INSTANCE_URL;
|
||||||
|
const dashboardUserEmail = process.env.DASHBOARD_USER_EMAIL;
|
||||||
|
const dashboardUserPassword = process.env.DASHBOARD_USER_PASSWORD;
|
||||||
|
|
||||||
|
export const configuration = z
|
||||||
|
.object({
|
||||||
|
instanceUrl: z.string().nonempty().url(),
|
||||||
|
dashboardUserEmail: z.string().nonempty(),
|
||||||
|
dashboardUserPassword: z.string().nonempty(),
|
||||||
|
})
|
||||||
|
.parse({
|
||||||
|
instanceUrl,
|
||||||
|
dashboardUserEmail,
|
||||||
|
dashboardUserPassword,
|
||||||
|
});
|
20
packages/e2e/setup/routing.ts
Normal file
20
packages/e2e/setup/routing.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { configuration } from "./configuration";
|
||||||
|
|
||||||
|
export const appUrls = (appUrl: string) => ({
|
||||||
|
manifest: new URL("/api/manifest", appUrl).href,
|
||||||
|
register: new URL("/api/register", appUrl).href,
|
||||||
|
});
|
||||||
|
|
||||||
|
const saleorUrls = (dashboardUrl: string) => ({
|
||||||
|
dashboard: {
|
||||||
|
homepage: new URL("/dashboard", dashboardUrl).href,
|
||||||
|
apps: new URL("/dashboard/apps", dashboardUrl).href,
|
||||||
|
appInstallPage: (appManifest: string) =>
|
||||||
|
new URL(`/dashboard/apps/install?manifestUrl=${appManifest}`, dashboardUrl).href,
|
||||||
|
},
|
||||||
|
api: new URL("/graphql/", dashboardUrl).href,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const routing = {
|
||||||
|
saleor: saleorUrls(configuration.instanceUrl),
|
||||||
|
};
|
66
packages/e2e/tests/apps-installation.spec.ts
Normal file
66
packages/e2e/tests/apps-installation.spec.ts
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import { test, expect, Page } from "@playwright/test";
|
||||||
|
import { logInIntoDashboard } from "./operations/log-in-to-dashboard";
|
||||||
|
import { installTheApp } from "./operations/install-app";
|
||||||
|
import { appUrls, routing } from "../setup/routing";
|
||||||
|
import { AppManifest } from "@saleor/app-sdk/types";
|
||||||
|
import { assertAppAvailable } from "./assertions/assert-app-available";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hardcoded list of every app deployed on staging and production.
|
||||||
|
* TODO: Eventually this should be the entry point and the list should be provided via env
|
||||||
|
*/
|
||||||
|
const apps: string[] = [
|
||||||
|
"taxes",
|
||||||
|
"crm",
|
||||||
|
"cms",
|
||||||
|
"emails-and-messages",
|
||||||
|
"product-feed",
|
||||||
|
"search",
|
||||||
|
"klaviyo",
|
||||||
|
"slack",
|
||||||
|
"invoices",
|
||||||
|
"data-importer",
|
||||||
|
].reduce((urls, appSegment) => {
|
||||||
|
urls.push(`https://${appSegment}.saleor.app`);
|
||||||
|
urls.push(`https://${appSegment}.staging.saleor.app`);
|
||||||
|
|
||||||
|
return urls;
|
||||||
|
}, []);
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* test.describe.configure({
|
||||||
|
* mode: "parallel",
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Enable parallel mode. It cant work with beforeAll.
|
||||||
|
*/
|
||||||
|
test.describe("Apps Installation", () => {
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
if (page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("beforeAll run");
|
||||||
|
|
||||||
|
page = await browser.newPage();
|
||||||
|
|
||||||
|
await logInIntoDashboard({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const appUrl of apps) {
|
||||||
|
test(`App: "${appUrl}" can be installed in the dashboard`, async () => {
|
||||||
|
const appManifestUrl = appUrl + "/api/manifest";
|
||||||
|
|
||||||
|
await installTheApp({ page, appManifest: appManifestUrl }); // todo extract to helper
|
||||||
|
|
||||||
|
const appManifest = (await fetch(appManifestUrl).then((r) => r.json())) as AppManifest;
|
||||||
|
const appName = appManifest.name;
|
||||||
|
|
||||||
|
await assertAppAvailable({ page, appName });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { Page, test, expect } from "@playwright/test";
|
||||||
|
import { logInIntoDashboard } from "../../operations/log-in-to-dashboard";
|
||||||
|
import { openTheApp } from "../../operations/open-app";
|
||||||
|
|
||||||
|
test.describe("Klaviyo Configuration", () => {
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
if (page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("beforeAll run");
|
||||||
|
|
||||||
|
page = await browser.newPage();
|
||||||
|
|
||||||
|
await logInIntoDashboard({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test assumes app is installed
|
||||||
|
test("App can be configured @stable @critical", async () => {
|
||||||
|
await openTheApp({ page, appName: "Klaviyo" });
|
||||||
|
|
||||||
|
// todo make more strict selector
|
||||||
|
const iframeLocator = page.frameLocator("iframe");
|
||||||
|
|
||||||
|
await expect(iframeLocator.getByLabel("CUSTOMER_CREATED_METRIC")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByLabel("FULFILLMENT_CREATED_METRIC")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByLabel("ORDER_CREATED_METRIC")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByLabel("ORDER_FULLY_PAID_METRIC")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByLabel("PUBLIC_TOKEN")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { expect, FrameLocator } from "@playwright/test";
|
||||||
|
|
||||||
|
export const assertAppRender = async (iframeLocator: FrameLocator) => {
|
||||||
|
await expect(iframeLocator.getByTestId("root-heading")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByTestId("s3-configuration-section")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByTestId("channels-configuration-section")).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByTestId("categories-mapping-section")).toBeVisible();
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { FrameLocator, Locator } from "@playwright/test";
|
||||||
|
|
||||||
|
export const fillAwsS3Form = async (iframeLocator: FrameLocator) => {
|
||||||
|
await iframeLocator.getByLabel("Amazon access key ID").fill("test-id");
|
||||||
|
await iframeLocator.getByLabel("Amazon secret access key").fill("test-secret");
|
||||||
|
await iframeLocator.getByLabel("Bucket name").fill("test-bucket");
|
||||||
|
await iframeLocator.getByLabel("Bucket region").fill("eu-west-1");
|
||||||
|
|
||||||
|
await iframeLocator.getByText("Save bucket configuration").click();
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { FrameLocator } from "@playwright/test";
|
||||||
|
|
||||||
|
export const fillChannelConfig = async (iframeLocator: FrameLocator) => {
|
||||||
|
const sectionsSelector = await iframeLocator.getByTestId("channels-configuration-section");
|
||||||
|
|
||||||
|
const channelRow = sectionsSelector.getByText("Default channel"); // todo add test-id
|
||||||
|
|
||||||
|
channelRow.click();
|
||||||
|
|
||||||
|
await sectionsSelector.getByLabel("Storefront URL").fill("https://www.example.com");
|
||||||
|
await sectionsSelector
|
||||||
|
.getByLabel("Storefront product URL")
|
||||||
|
.fill("https://www.example.com/{productId}");
|
||||||
|
|
||||||
|
await sectionsSelector.getByText("Save channel settings").click();
|
||||||
|
};
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { FrameLocator, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
export const navigateToCategoryMapping = async (iframeLocator: FrameLocator) => {
|
||||||
|
await iframeLocator.getByText("Map categories").click({ force: true });
|
||||||
|
|
||||||
|
await expect(iframeLocator.getByTestId("categories-mapping-container")).toBeVisible();
|
||||||
|
|
||||||
|
// todo doesnt load, probably app must be optimized
|
||||||
|
await expect(iframeLocator.getByText("Accessories")).toBeVisible({
|
||||||
|
timeout: 120000,
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { FrameLocator } from "@playwright/test";
|
||||||
|
|
||||||
|
export const setCategoryMapping = async (iframeLocator: FrameLocator) => {
|
||||||
|
await iframeLocator.locator("select").first().selectOption({ index: 0 });
|
||||||
|
|
||||||
|
await iframeLocator.getByText("Save").first().click();
|
||||||
|
};
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { Page, test, expect } from "@playwright/test";
|
||||||
|
import { logInIntoDashboard } from "../../operations/log-in-to-dashboard";
|
||||||
|
import { openTheApp } from "../../operations/open-app";
|
||||||
|
import { fillAwsS3Form } from "./operations/fill-aws-s3-form";
|
||||||
|
import { assertAppRender } from "./assertions/assert-app-render";
|
||||||
|
import { fillChannelConfig } from "./operations/fill-channel-config";
|
||||||
|
import { setCategoryMapping } from "./operations/set-category-mapping";
|
||||||
|
import { navigateToCategoryMapping } from "./operations/navigate-to-category-mapping";
|
||||||
|
|
||||||
|
test.describe("Product Feed Configuration", () => {
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
if (page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("beforeAll run");
|
||||||
|
|
||||||
|
page = await browser.newPage();
|
||||||
|
|
||||||
|
await logInIntoDashboard({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test assumes app is installed
|
||||||
|
test("App can be configured @stable @critical", async () => {
|
||||||
|
await openTheApp({ page, appName: "Product Feed" });
|
||||||
|
|
||||||
|
// todo make more strict selector
|
||||||
|
const iframeLocator = page.frameLocator("iframe");
|
||||||
|
|
||||||
|
await assertAppRender(iframeLocator);
|
||||||
|
|
||||||
|
await fillAwsS3Form(iframeLocator);
|
||||||
|
|
||||||
|
await expect(page.getByText("Updated S3 configuration")).toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
|
await fillChannelConfig(iframeLocator);
|
||||||
|
|
||||||
|
await expect(page.getByText("Success")).toBeVisible({
|
||||||
|
timeout: 10000,
|
||||||
|
}); // todo add more meaningul message, only "success" is set
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test fails. Probably because of a very big list of Google categories that are fetched.
|
||||||
|
* TODO: Fix this in the app
|
||||||
|
*/
|
||||||
|
test.skip("App can be configured with categories mapping", async () => {
|
||||||
|
await openTheApp({ page, appName: "Product Feed" });
|
||||||
|
|
||||||
|
// todo make more strict selector
|
||||||
|
const iframeLocator = page.frameLocator("iframe");
|
||||||
|
|
||||||
|
await navigateToCategoryMapping(iframeLocator);
|
||||||
|
|
||||||
|
await setCategoryMapping(iframeLocator);
|
||||||
|
|
||||||
|
await expect(page.getByText("Success")).toBeVisible({ timeout: 10000 }); // todo add more meaningul message, only "success" is set
|
||||||
|
|
||||||
|
await iframeLocator.getByText("Configuration").click();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { expect, FrameLocator } from "@playwright/test";
|
||||||
|
|
||||||
|
export const assertAppRender = async (iframeLocator: FrameLocator) => {
|
||||||
|
/*
|
||||||
|
* TODO Add test-ids assertions after added to app
|
||||||
|
* todo assert empty state, but these tests must ensure app has fresh install
|
||||||
|
*/
|
||||||
|
await expect(
|
||||||
|
iframeLocator.getByRole("heading", {
|
||||||
|
name: "Tax providers",
|
||||||
|
})
|
||||||
|
).toBeVisible();
|
||||||
|
await expect(iframeLocator.getByRole("heading", { name: "Available channels" })).toBeVisible();
|
||||||
|
// await expect(iframeLocator.getByRole("heading", { name: "Tax code matcher" })).toBeVisible(); // todo enable when app enables
|
||||||
|
|
||||||
|
const addProviderButton = await iframeLocator.getByRole("button", {
|
||||||
|
name: new RegExp(/Add new|Add first provider/),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(addProviderButton).toBeVisible();
|
||||||
|
};
|
57
packages/e2e/tests/apps/taxes/taxes-configuration.spec.ts
Normal file
57
packages/e2e/tests/apps/taxes/taxes-configuration.spec.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import { Page, test, expect } from "@playwright/test";
|
||||||
|
import { logInIntoDashboard } from "../../operations/log-in-to-dashboard";
|
||||||
|
import { openTheApp } from "../../operations/open-app";
|
||||||
|
import { assertAppRender } from "./assertions/assert-app-render";
|
||||||
|
|
||||||
|
// Test assumes app is installed
|
||||||
|
test.describe("Taxes Configuration", () => {
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ browser }) => {
|
||||||
|
if (page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
page = await browser.newPage();
|
||||||
|
|
||||||
|
await logInIntoDashboard({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("App renders config screen @stable @critical", async () => {
|
||||||
|
await openTheApp({ page, appName: "Taxes" });
|
||||||
|
|
||||||
|
// todo make more strict selector
|
||||||
|
const iframeLocator = page.frameLocator("iframe");
|
||||||
|
|
||||||
|
await assertAppRender(iframeLocator);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("App can configure new Taxjar provider @taxjar", async () => {
|
||||||
|
await openTheApp({ page, appName: "Taxes" });
|
||||||
|
|
||||||
|
// todo make more strict selector
|
||||||
|
const iframeLocator = page.frameLocator("iframe");
|
||||||
|
|
||||||
|
await iframeLocator
|
||||||
|
.getByRole("button", {
|
||||||
|
name: new RegExp(/Add new|Add first provider/),
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await iframeLocator.getByRole("button", { name: "Choose" }).first().click(); // todo - test id
|
||||||
|
|
||||||
|
await iframeLocator.getByLabel("Configuration name").fill("Test Taxjar provider");
|
||||||
|
await iframeLocator.getByLabel("API key").fill("TEST");
|
||||||
|
await iframeLocator.getByLabel("Street").fill("Street");
|
||||||
|
await iframeLocator.getByLabel("City").fill("City");
|
||||||
|
await iframeLocator.getByLabel("State").fill("State");
|
||||||
|
await iframeLocator.getByRole("combobox", { name: "Country *" }).click();
|
||||||
|
await iframeLocator.getByText("Albania").click();
|
||||||
|
await iframeLocator.getByLabel("Zip").fill("Zip");
|
||||||
|
|
||||||
|
await iframeLocator.getByRole("button", { name: "Save" }).first().click(); // todo - test id
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo
|
||||||
|
test.skip("App can configure new Avalara provider @avalara", async () => {});
|
||||||
|
});
|
19
packages/e2e/tests/assertions/assert-app-available.ts
Normal file
19
packages/e2e/tests/assertions/assert-app-available.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { expect, Page } from "@playwright/test";
|
||||||
|
import { routing } from "../../setup/routing";
|
||||||
|
import { configuration } from "../../setup/configuration";
|
||||||
|
|
||||||
|
interface checkIfAppIsAvailableArgs {
|
||||||
|
page: Page;
|
||||||
|
appName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const assertAppAvailable = async ({ page, appName }: checkIfAppIsAvailableArgs) => {
|
||||||
|
await page.goto(routing.saleor.dashboard.apps, { timeout: 20000, waitUntil: "load" });
|
||||||
|
|
||||||
|
// todo need data-testid. this element is not unique
|
||||||
|
const appEntry = await page.getByText(appName).first();
|
||||||
|
|
||||||
|
await expect(appEntry).toBeVisible();
|
||||||
|
|
||||||
|
await expect(await page.getByText("Problem occured during installation.")).toBeHidden();
|
||||||
|
};
|
22
packages/e2e/tests/operations/install-app.ts
Normal file
22
packages/e2e/tests/operations/install-app.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Page } from "@playwright/test";
|
||||||
|
import { routing } from "../../setup/routing";
|
||||||
|
|
||||||
|
interface InstallTheAppArgs {
|
||||||
|
page: Page;
|
||||||
|
appManifest: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const installTheApp = async ({ page, appManifest }: InstallTheAppArgs) => {
|
||||||
|
// got to Apps page, assuming user is logged in
|
||||||
|
await page.goto(routing.saleor.dashboard.appInstallPage(appManifest), {
|
||||||
|
timeout: 20000,
|
||||||
|
waitUntil: "load",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Navigated to", page.url());
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Install App" }).click();
|
||||||
|
|
||||||
|
// wait for the toast
|
||||||
|
await page.getByText("App installed").isVisible();
|
||||||
|
};
|
25
packages/e2e/tests/operations/log-in-to-dashboard.ts
Normal file
25
packages/e2e/tests/operations/log-in-to-dashboard.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Page, expect } from "@playwright/test";
|
||||||
|
import { configuration } from "../../setup/configuration";
|
||||||
|
import { routing } from "../../setup/routing";
|
||||||
|
|
||||||
|
interface LogInIntoDashboardArgs {
|
||||||
|
page: Page;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const logInIntoDashboard = async ({ page }: LogInIntoDashboardArgs) => {
|
||||||
|
console.log("Will redirect to", routing.saleor.dashboard.homepage);
|
||||||
|
|
||||||
|
await page.goto(routing.saleor.dashboard.homepage, { timeout: 20000, waitUntil: "load" });
|
||||||
|
|
||||||
|
const url = page.url();
|
||||||
|
|
||||||
|
await page.locator('[data-test-id="email"]').click();
|
||||||
|
await page.locator('[data-test-id="email"]').fill(configuration.dashboardUserEmail);
|
||||||
|
await page.locator('[data-test-id="email"]').press("Tab");
|
||||||
|
await page.locator('[data-test-id="password"]').fill(configuration.dashboardUserPassword);
|
||||||
|
await page.locator('[data-test-id="submit"]').click();
|
||||||
|
|
||||||
|
await expect(page.locator('[data-test-id="welcome-header"]')).toBeVisible();
|
||||||
|
|
||||||
|
console.log("Logged in");
|
||||||
|
};
|
21
packages/e2e/tests/operations/open-app.ts
Normal file
21
packages/e2e/tests/operations/open-app.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { Page, expect } from "@playwright/test";
|
||||||
|
import { routing } from "../../setup/routing";
|
||||||
|
|
||||||
|
interface InstallTheAppArgs {
|
||||||
|
page: Page;
|
||||||
|
appName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const openTheApp = async ({ page, appName }: InstallTheAppArgs) => {
|
||||||
|
// got to Apps page, assuming user is logged in
|
||||||
|
await page.goto(routing.saleor.dashboard.apps, {
|
||||||
|
waitUntil: "load",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Navigated to", page.url());
|
||||||
|
|
||||||
|
// todo it must be test-id because we also have same name in appstore list
|
||||||
|
await page.getByText(appName).first().click();
|
||||||
|
|
||||||
|
await expect(page.url()).toMatch(new RegExp("https:\\/\\/.*\\/dashboard\\/apps\\/.*\\/app"));
|
||||||
|
};
|
12
packages/e2e/turbo.json
Normal file
12
packages/e2e/turbo.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://turbo.build/schema.json",
|
||||||
|
"extends": ["//"],
|
||||||
|
"pipeline": {
|
||||||
|
"e2e": {
|
||||||
|
"env": ["INSTANCE_URL", "DASHBOARD_USER_PASSWORD", "DASHBOARD_USER_EMAIL"]
|
||||||
|
},
|
||||||
|
"e2e:ui": {
|
||||||
|
"env": ["INSTANCE_URL", "DASHBOARD_USER_PASSWORD", "DASHBOARD_USER_EMAIL"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
"@material-ui/core": "^4.12.4",
|
"@material-ui/core": "^4.12.4",
|
||||||
"@material-ui/icons": "^4.11.3",
|
"@material-ui/icons": "^4.11.3",
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
"@material-ui/lab": "4.0.0-alpha.61",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.41.0",
|
||||||
"@saleor/macaw-ui": "^0.7.2",
|
"@saleor/macaw-ui": "^0.7.2",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"typescript": "5.1.3"
|
"typescript": "5.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.41.0",
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.95",
|
||||||
"@types/react": "18.2.5",
|
"@types/react": "18.2.5",
|
||||||
"@types/react-dom": "18.2.5",
|
"@types/react-dom": "18.2.5",
|
||||||
|
|
557
pnpm-lock.yaml
557
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://turbo.build/schema.json",
|
"$schema": "https://turbo.build/schema.json",
|
||||||
"globalDependencies": ["**/.env.*local"],
|
"globalDependencies": ["**/.env.*local"],
|
||||||
"globalEnv": ["VERCEL_ENV", "APP_LOG_LEVEL", "NODE_ENV"],
|
"globalEnv": ["VERCEL_ENV", "APP_LOG_LEVEL", "NODE_ENV", "CI"],
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"build": {
|
"build": {
|
||||||
"env": ["NEXT_PUBLIC_VERCEL_ENV"],
|
"env": ["NEXT_PUBLIC_VERCEL_ENV"],
|
||||||
|
|
Loading…
Reference in a new issue