Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests #12

Merged
merged 3 commits into from
May 27, 2024
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
23 changes: 21 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
on:
pull_request:
name: lint
name: test
jobs:
eslint:
runs-on: ubuntu-latest
Expand All @@ -12,4 +12,23 @@ jobs:
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run lint

test:
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ tsconfig.tsbuildinfo
.DS_Store?
ehthumbs.db
Thumbs.db
dist
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
60 changes: 60 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
version: '3.7'

x-service: &service
environment:
APP_ENV: prod
APP_DEBUG: 1
PROJECT_ROOT: /var/www/html
MYSQL_ROOT_PASSWORD: app
MYSQL_DATABASE: root
ADMINER_DEFAULT_SERVER: database
SHOPWARE_HTTP_CACHE_ENABLED: 0
DATABASE_URL: mysql://root:app@database:3306/install_test
APP_SECRET: secret
APP_URL: http://localhost:8000
# TEST_WEB_INSTALLER: 1
# SHOPWARE_SKIP_WEBINSTALLER: 0
# SHOPWARE_DISABLE_UPDATE_CHECK: '0'
# SW_RECOVERY_NEXT_VERSION: 6.6.9999999.9999999
# SW_RECOVERY_NEXT_BRANCH: dev-trunk
# SW_RECOVERY_REPOSITORY: '{"type": "path", "url": "custom/packages/*", "options": { "symlink": true } }'
PHP_OPCACHE_VALIDATE_TIMESTAMPS: 1
networks:
- local

services:
database:
<<: *service
image: mysql:8.0
entrypoint:
[
"sh",
"-c",
"docker-entrypoint.sh mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password --sql-require-primary-key=ON"
]

ports:
- '3306:3306'
tmpfs:
- /var/lib/mysql:uid=999,gid=999

adminer:
<<: *service
image: adminer:latest
depends_on: [ database ]
ports:
- '8080:8080'

shopware:
<<: *service
# TODO: build trunk tag
image: ghcr.io/shopware/shopware/ci-e2e:trunk
depends_on: [ database ]
ports:
- '8000:8000'
# mount platform
# volumes:
# - ../../:/var/www/html/custom/platform

networks:
local:
41 changes: 41 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test';

process.env['SHOPWARE_ADMIN_USERNAME'] = process.env['SHOPWARE_ADMIN_USERNAME'] || 'admin';
process.env['SHOPWARE_ADMIN_PASSWORD'] = process.env['SHOPWARE_ADMIN_PASSWORD'] || 'shopware';

process.env['APP_URL'] = process.env['APP_URL'] ?? 'http://localhost:8000';

// make sure APP_URL ends with a slash
process.env['APP_URL'] = (process.env['APP_URL'] ?? '').replace(/\/+$/, '') + '/';
if (process.env['ADMIN_URL']) {
process.env['ADMIN_URL'] = process.env['ADMIN_URL'].replace(/\/+$/, '') + '/';
} else {
process.env['ADMIN_URL'] = process.env['APP_URL'] + 'admin/';
}

export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : 1,
reporter: 'html',
use: {
baseURL: process.env['APP_URL'],
trace: 'on',
video: 'off',
},
// We abuse this to wait for the external webserver
webServer: {
command: 'docker compose up',
url: process.env['APP_URL'],
reuseExistingServer: true,
timeout: 120000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
73 changes: 61 additions & 12 deletions src/data-fixtures/Product/DigitalProduct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,59 @@ import type { FixtureTypes } from '../../types/FixtureTypes';
import type { components } from '@shopware/api-client/admin-api-types';

export const DigitalProductData = base.extend<FixtureTypes>({
DigitalProductData: async ({ IdProvider, AdminApiContext, ProductData }, use) => {
DigitalProductData: async ({ IdProvider, AdminApiContext, SalesChannelBaseConfig, DefaultSalesChannel }, use) => {

// Generate unique IDs
const { id: productId, uuid: productUuid } = IdProvider.getIdPair();
const productName = `Digital_Product_test_${productId}`;

// Create product
const productResponse = await AdminApiContext.post('./product?_response', {
data: {
active: true,
stock: 10,
taxId: SalesChannelBaseConfig.taxId,
id: productUuid,
name: productName,
productNumber: 'Product-' + productId,
price: [
{
currencyId: SalesChannelBaseConfig.eurCurrencyId,
gross: 10,
linked: false,
net: 8.4,
},
],
purchasePrices: [
{
currencyId: SalesChannelBaseConfig.eurCurrencyId,
gross: 8,
linked: false,
net: 6.7,
},
],
visibilities: [
{
salesChannelId: DefaultSalesChannel.salesChannel.id,
visibility: 30,
},
],
categories: [
{
id: DefaultSalesChannel.salesChannel.navigationCategoryId,
},
],
},
});
expect(productResponse.ok()).toBeTruthy();

const { data: productData } = (await productResponse.json()) as { data: components['schemas']['Product'] };

// Create new Media resource in the default folder for digital product media
const newMediaResource = await AdminApiContext.post('./media?_response', {
data: {
private: false,
},
},
});

expect(newMediaResource.ok()).toBeTruthy();
Expand All @@ -20,16 +66,16 @@ export const DigitalProductData = base.extend<FixtureTypes>({
const filename = 'testfile_' + IdProvider.getUniqueName();
const fileContent = 'This is a test file to test digital product download';
const newMediaUpload = await AdminApiContext.post(`./_action/media/${newMediaId}/upload?extension=txt&fileName=${filename}&_response`, {
headers:{
headers: {
'content-type': 'application/octet-stream',
},
data: fileContent,
});
});
expect(newMediaUpload.ok()).toBeTruthy();

const productDownloadResponse = await AdminApiContext.post(`./product-download?_response`, {
data: {
productId: ProductData.id,
productId: productData.id,
mediaId: newMediaId,
},
});
Expand All @@ -38,9 +84,10 @@ export const DigitalProductData = base.extend<FixtureTypes>({
const { data: productDownload } = await productDownloadResponse.json();

const returnData = {
product: ProductData,
product: productData,
fileContent,
}
}

// Use product data in the test
await use(returnData);

Expand All @@ -52,20 +99,20 @@ export const DigitalProductData = base.extend<FixtureTypes>({
{
type: 'equals',
field: 'lineItems.productId',
value: ProductData.id,
value: productData.id,
},
],
},
});
expect(orderSearchResponse.ok()).toBeTruthy();

const { data: ordersWithDigitalProduct } = (await orderSearchResponse.json()) as { data: components['schemas']['Order'][]};
const { data: ordersWithDigitalProduct } = (await orderSearchResponse.json()) as { data: components['schemas']['Order'][] };

// Delete Orders using the digital product, to be able to delete the uploaded media file
for (const order of ordersWithDigitalProduct) {
for (const order of ordersWithDigitalProduct) {
const deleteOrderResponse = await AdminApiContext.delete(`./order/${order.id}`);
expect(deleteOrderResponse.ok()).toBeTruthy();
}
}

// Unlink the media file from the product by deleting the product-download
const unlinkMediaResponse = await AdminApiContext.delete(`./product-download/${productDownload.id}`);
Expand All @@ -75,6 +122,8 @@ export const DigitalProductData = base.extend<FixtureTypes>({
const cleanupMediaResponse = await AdminApiContext.delete(`./media/${newMediaId}`);
expect(cleanupMediaResponse.ok()).toBeTruthy();

// Deletion of product is done in Product.data.ts
// Delete product after the test is done
const cleanupResponse = await AdminApiContext.delete(`./product/${productUuid}`);
expect(cleanupResponse.ok()).toBeTruthy();
},
});
40 changes: 11 additions & 29 deletions src/data-fixtures/Product/Product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,41 +34,23 @@ export const ProductData = base.extend<FixtureTypes>({
net: 6.7,
},
],
visibilities: [
{
salesChannelId: DefaultSalesChannel.salesChannel.id,
visibility: 30,
},
],
categories: [
{
id: DefaultSalesChannel.salesChannel.navigationCategoryId,
},
],
},
});
expect(productResponse.ok()).toBeTruthy();

const { data: product } = (await productResponse.json()) as { data: components['schemas']['Product'] };

// Assign product to sales channel
const syncResp = await AdminApiContext.post('./_action/sync', {
data: {
'add product to sales channel': {
entity: 'product_visibility',
action: 'upsert',
payload: [
{
productId: product.id,
salesChannelId: DefaultSalesChannel.salesChannel.id,
visibility: 30,
},
],
},
'add product to root navigation': {
entity: 'product_category',
action: 'upsert',
payload: [
{
productId: product.id,
categoryId: DefaultSalesChannel.salesChannel.navigationCategoryId,
},
],
},
},
});

expect(syncResp.ok()).toBeTruthy();

// Use product data in the test
await use(product);

Expand Down
33 changes: 33 additions & 0 deletions tests/Fixtures.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { test, expect } from '../src/index';

test('All fixtures', async ({
AdminApiContext,
StoreApiContext,
DefaultSalesChannel,
IdProvider,

CartWithProductData,
PromotionWithCodeData,

MediaData,
OrderData,
DigitalProductData,
ProductData,
PropertiesData,
}) => {
const { id, uuid } = IdProvider.getIdPair();

await expect(id).toBeTruthy();
await expect(uuid).toBeTruthy();

await expect(AdminApiContext).toBeInstanceOf(Object);
await expect(StoreApiContext).toBeInstanceOf(Object);
await expect(DefaultSalesChannel).toBeInstanceOf(Object);
await expect(CartWithProductData).toBeInstanceOf(Object);
await expect(PromotionWithCodeData).toBeInstanceOf(Object);
await expect(MediaData).toBeInstanceOf(Object);
await expect(OrderData).toBeInstanceOf(Object);
await expect(DigitalProductData).toBeInstanceOf(Object);
await expect(ProductData).toBeInstanceOf(Object);
await expect(PropertiesData).toBeInstanceOf(Object);
});