shopware-e2e-testkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -0
- package/bin/shopware-testkit.js +31 -0
- package/dist/commands/init.js +68 -0
- package/dist/commands/run.js +70 -0
- package/dist/index.js +21 -0
- package/dist/pages/BasePage.js +29 -0
- package/dist/pages/CheckoutPage.js +59 -0
- package/dist/pages/HomePage.js +13 -0
- package/dist/pages/ProductPage.js +24 -0
- package/dist/pages/SearchPage.js +16 -0
- package/dist/tests/storefront-smoke.spec.js +73 -0
- package/package.json +34 -0
- package/templates/.env.example +8 -0
- package/templates/playwright.config.ts +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Shopware 6 E2E Smoke Testkit
|
|
2
|
+
|
|
3
|
+
A ready-to-use E2E/smoke testkit for Shopware 6 Storefront, based on Playwright.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- ✅ Quick setup with `init` command.
|
|
7
|
+
- ✅ Pre-configured smoke tests for core storefront functionality.
|
|
8
|
+
- ✅ Page Object Model (POM) based architecture for easy maintenance.
|
|
9
|
+
- ✅ Configurable via environment variables.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install --save-dev shopware-e2e-testkit
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Getting Started
|
|
18
|
+
|
|
19
|
+
### 1. Initialize the testkit
|
|
20
|
+
|
|
21
|
+
Run the following command in your project root:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx shopware-testkit init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This will create:
|
|
28
|
+
- `playwright.config.ts`: Playwright configuration.
|
|
29
|
+
- `.env`: Environment variables for your shop URL.
|
|
30
|
+
- `.env.example`: Template for environment variables.
|
|
31
|
+
|
|
32
|
+
### 2. Configure your Shop URL
|
|
33
|
+
|
|
34
|
+
Open the `.env` file and set your `SALESCHANNEL_URL`:
|
|
35
|
+
|
|
36
|
+
```env
|
|
37
|
+
SALESCHANNEL_URL=https://your-shopware-6-store.com
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 3. Run the tests
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx shopware-testkit run
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also specify the URL directly via CLI:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx shopware-testkit run --url https://another-store.com
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Debugging
|
|
53
|
+
|
|
54
|
+
You can debug the tests using standard Playwright features:
|
|
55
|
+
|
|
56
|
+
- **UI Mode**: Opens an interactive UI to step through tests.
|
|
57
|
+
```bash
|
|
58
|
+
npx shopware-testkit run --ui
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **Debug Mode**: Opens the Playwright Inspector.
|
|
62
|
+
```bash
|
|
63
|
+
npx shopware-testkit run --debug
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Continuous Integration (Bitbucket Pipelines)
|
|
67
|
+
|
|
68
|
+
To run the smoke tests automatically in your Bitbucket Pipelines, you can add a step to your `bitbucket-pipelines.yml`.
|
|
69
|
+
|
|
70
|
+
### Example Configuration
|
|
71
|
+
|
|
72
|
+
Using the official Playwright Docker image ensures all browser dependencies are met. You can add this as a separate step in your pipeline:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
pipelines:
|
|
76
|
+
default:
|
|
77
|
+
- step:
|
|
78
|
+
name: Shopware E2E Smoke Tests
|
|
79
|
+
image: mcr.microsoft.com/playwright:v1.49.1-jammy
|
|
80
|
+
caches:
|
|
81
|
+
- node
|
|
82
|
+
script:
|
|
83
|
+
- npm ci
|
|
84
|
+
# The testkit picks up SALESCHANNEL_URL and SEARCH_TERM from the environment
|
|
85
|
+
- npx shopware-testkit run
|
|
86
|
+
artifacts:
|
|
87
|
+
- playwright-report/**
|
|
88
|
+
|
|
89
|
+
# Manual trigger for specific environment
|
|
90
|
+
custom:
|
|
91
|
+
smoke-tests-staging:
|
|
92
|
+
- step:
|
|
93
|
+
name: Shopware E2E Smoke Tests (Staging)
|
|
94
|
+
image: mcr.microsoft.com/playwright:v1.49.1-jammy
|
|
95
|
+
script:
|
|
96
|
+
- npm ci
|
|
97
|
+
- SALESCHANNEL_URL="https://staging.yourshop.com" npx shopware-testkit run
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Environment Variables
|
|
101
|
+
Make sure to define the following **Repository Variables** in Bitbucket (**Repository settings > Pipelines > Repository variables**):
|
|
102
|
+
|
|
103
|
+
- `SALESCHANNEL_URL`: The URL of your Shopware storefront (e.g., `https://staging.yourshop.com`).
|
|
104
|
+
- `SEARCH_TERM`: (Optional) The product name to search for.
|
|
105
|
+
|
|
106
|
+
## Included Tests
|
|
107
|
+
|
|
108
|
+
The smoke testkit covers:
|
|
109
|
+
- **Homepage**: Verifies that the home page loads and essential elements (logo, navigation, footer) are visible.
|
|
110
|
+
- **Search**: Verifies that the search functionality returns results.
|
|
111
|
+
- **Product Detail Page (PDP)**: Verifies that product details and price are displayed.
|
|
112
|
+
- **Cart**: Verifies that products can be added to the cart.
|
|
113
|
+
- **Checkout**: Verifies the guest checkout flow until the confirmation page (without placing an actual order).
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const { init } = require('../dist/commands/init');
|
|
5
|
+
const { run } = require('../dist/commands/run');
|
|
6
|
+
const packageJson = require('../package.json');
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('shopware-testkit')
|
|
10
|
+
.description('E2E/smoke testkit for Shopware 6 Storefront')
|
|
11
|
+
.version(packageJson.version);
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Initialize the testkit in the current project')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
await init();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.command('run')
|
|
22
|
+
.description('Run the smoke tests')
|
|
23
|
+
.option('-u, --url <url>', 'Target Shopware URL')
|
|
24
|
+
.option('-s, --search <term>', 'Search term for product tests')
|
|
25
|
+
.option('--debug', 'Run tests in debug mode (Playwright Inspector)')
|
|
26
|
+
.option('--ui', 'Run tests in Playwright UI mode')
|
|
27
|
+
.action((options) => {
|
|
28
|
+
run(options);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.init = init;
|
|
37
|
+
const fs = __importStar(require("fs-extra"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
async function init() {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const templateDir = path.resolve(__dirname, '../../templates');
|
|
42
|
+
console.log('🚀 Initializing Shopware E2E Testkit...');
|
|
43
|
+
const filesToCopy = [
|
|
44
|
+
{ src: 'playwright.config.ts', dest: 'playwright.config.ts' },
|
|
45
|
+
{ src: '.env.example', dest: '.env.example' },
|
|
46
|
+
];
|
|
47
|
+
for (const file of filesToCopy) {
|
|
48
|
+
const srcPath = path.join(templateDir, file.src);
|
|
49
|
+
const destPath = path.join(cwd, file.dest);
|
|
50
|
+
if (await fs.pathExists(destPath)) {
|
|
51
|
+
console.warn(`⚠️ File ${file.dest} already exists, skipping.`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
await fs.copy(srcPath, destPath);
|
|
55
|
+
console.log(`✅ Created ${file.dest}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Create .env if it doesn't exist
|
|
59
|
+
const envPath = path.join(cwd, '.env');
|
|
60
|
+
if (!(await fs.pathExists(envPath))) {
|
|
61
|
+
await fs.copy(path.join(templateDir, '.env.example'), envPath);
|
|
62
|
+
console.log('✅ Created .env (from .env.example)');
|
|
63
|
+
}
|
|
64
|
+
console.log('\n🎉 Initialization complete!');
|
|
65
|
+
console.log('Next steps:');
|
|
66
|
+
console.log('1. Update your .env file with your shop URL.');
|
|
67
|
+
console.log('2. Run your tests with: npx shopware-testkit run');
|
|
68
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.run = run;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
function run(options) {
|
|
40
|
+
console.log('🔍 Running Shopware Smoke Tests...');
|
|
41
|
+
const env = { ...process.env };
|
|
42
|
+
if (options.url) {
|
|
43
|
+
env.SALESCHANNEL_URL = options.url;
|
|
44
|
+
console.log(`🌐 Target URL set to: ${options.url}`);
|
|
45
|
+
}
|
|
46
|
+
if (options.search) {
|
|
47
|
+
env.SEARCH_TERM = options.search;
|
|
48
|
+
console.log(`🔍 Search term set to: ${options.search}`);
|
|
49
|
+
}
|
|
50
|
+
const configPath = path.resolve(process.cwd(), 'playwright.config.ts');
|
|
51
|
+
const testsPath = path.resolve(__dirname, '../tests');
|
|
52
|
+
let command = `npx playwright test "${testsPath}" --config="${configPath}"`;
|
|
53
|
+
if (options.debug) {
|
|
54
|
+
command += ' --debug';
|
|
55
|
+
}
|
|
56
|
+
if (options.ui) {
|
|
57
|
+
command += ' --ui';
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
// Run playwright test using the local config and pointing to the bundled tests
|
|
61
|
+
(0, child_process_1.execSync)(command, {
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
env
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('❌ Tests failed!');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./pages/BasePage"), exports);
|
|
18
|
+
__exportStar(require("./pages/HomePage"), exports);
|
|
19
|
+
__exportStar(require("./pages/SearchPage"), exports);
|
|
20
|
+
__exportStar(require("./pages/ProductPage"), exports);
|
|
21
|
+
__exportStar(require("./pages/CheckoutPage"), exports);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BasePage = void 0;
|
|
4
|
+
class BasePage {
|
|
5
|
+
page;
|
|
6
|
+
constructor(page) {
|
|
7
|
+
this.page = page;
|
|
8
|
+
}
|
|
9
|
+
async goto(path = '/') {
|
|
10
|
+
await this.page.goto(path);
|
|
11
|
+
}
|
|
12
|
+
get headerLogo() {
|
|
13
|
+
return this.page.locator('.header-logo-main-link').first();
|
|
14
|
+
}
|
|
15
|
+
get searchInput() {
|
|
16
|
+
return this.page.locator('input[name="search"]').first();
|
|
17
|
+
}
|
|
18
|
+
get searchButton() {
|
|
19
|
+
return this.page.locator('.header-search-btn').first();
|
|
20
|
+
}
|
|
21
|
+
get cartButton() {
|
|
22
|
+
return this.page.locator('.header-cart-btn').first();
|
|
23
|
+
}
|
|
24
|
+
async searchFor(term) {
|
|
25
|
+
await this.searchInput.fill(term);
|
|
26
|
+
await this.searchButton.click();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.BasePage = BasePage;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CheckoutPage = void 0;
|
|
4
|
+
const BasePage_1 = require("./BasePage");
|
|
5
|
+
class CheckoutPage extends BasePage_1.BasePage {
|
|
6
|
+
get offcanvasCartCheckoutButton() {
|
|
7
|
+
return this.page.locator('.cart-offcanvas .begin-checkout-btn').first();
|
|
8
|
+
}
|
|
9
|
+
get guestCheckoutButton() {
|
|
10
|
+
return this.page.locator('.login-card .btn-secondary').first();
|
|
11
|
+
}
|
|
12
|
+
// Guest registration selectors
|
|
13
|
+
get salutationSelect() { return this.page.locator('#personalSalutation'); }
|
|
14
|
+
get firstNameInput() { return this.page.locator('#personalFirstName'); }
|
|
15
|
+
get lastNameInput() { return this.page.locator('#personalLastName'); }
|
|
16
|
+
get emailInput() { return this.page.locator('#personalMail'); }
|
|
17
|
+
get streetInput() { return this.page.locator('#billingAddressAddressStreet'); }
|
|
18
|
+
get zipcodeInput() { return this.page.locator('#billingAddressAddressZipcode'); }
|
|
19
|
+
get cityInput() { return this.page.locator('#billingAddressAddressCity'); }
|
|
20
|
+
get countrySelect() { return this.page.locator('#billingAddressAddressCountry'); }
|
|
21
|
+
get submitGuestRegistrationButton() {
|
|
22
|
+
return this.page.locator('.checkout-register-submit');
|
|
23
|
+
}
|
|
24
|
+
get confirmOrderButton() {
|
|
25
|
+
return this.page.locator('#confirmFormSubmit');
|
|
26
|
+
}
|
|
27
|
+
get checkoutAsideSummary() {
|
|
28
|
+
return this.page.locator('.checkout-aside-summary');
|
|
29
|
+
}
|
|
30
|
+
get grandTotal() {
|
|
31
|
+
return this.page.locator('.checkout-aside-summary-total').first();
|
|
32
|
+
}
|
|
33
|
+
get checkoutFinishTitle() {
|
|
34
|
+
return this.page.locator('.finish-header');
|
|
35
|
+
}
|
|
36
|
+
async goToCheckoutFromOffcanvas() {
|
|
37
|
+
await this.offcanvasCartCheckoutButton.click();
|
|
38
|
+
}
|
|
39
|
+
async fillGuestData(data) {
|
|
40
|
+
await this.salutationSelect.selectOption({ label: data.salutation });
|
|
41
|
+
await this.firstNameInput.fill(data.firstName);
|
|
42
|
+
await this.lastNameInput.fill(data.lastName);
|
|
43
|
+
await this.emailInput.fill(data.email);
|
|
44
|
+
await this.streetInput.fill(data.street);
|
|
45
|
+
await this.zipcodeInput.fill(data.zipcode);
|
|
46
|
+
await this.cityInput.fill(data.city);
|
|
47
|
+
await this.countrySelect.selectOption({ label: data.country });
|
|
48
|
+
await this.submitGuestRegistrationButton.click();
|
|
49
|
+
}
|
|
50
|
+
async placeOrder() {
|
|
51
|
+
// Wait for the TOS checkbox if it exists and check it
|
|
52
|
+
const tosCheckbox = this.page.locator('#tos');
|
|
53
|
+
if (await tosCheckbox.isVisible()) {
|
|
54
|
+
await tosCheckbox.check();
|
|
55
|
+
}
|
|
56
|
+
await this.confirmOrderButton.click();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.CheckoutPage = CheckoutPage;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HomePage = void 0;
|
|
4
|
+
const BasePage_1 = require("./BasePage");
|
|
5
|
+
class HomePage extends BasePage_1.BasePage {
|
|
6
|
+
get footer() {
|
|
7
|
+
return this.page.locator('.footer-main');
|
|
8
|
+
}
|
|
9
|
+
get mainNavigation() {
|
|
10
|
+
return this.page.locator('.main-navigation-menu');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.HomePage = HomePage;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProductPage = void 0;
|
|
4
|
+
const BasePage_1 = require("./BasePage");
|
|
5
|
+
class ProductPage extends BasePage_1.BasePage {
|
|
6
|
+
get buyButton() {
|
|
7
|
+
return this.page.locator('.buy-widget .btn-buy').first();
|
|
8
|
+
}
|
|
9
|
+
get productPrice() {
|
|
10
|
+
return this.page.locator('.product-detail-price').first();
|
|
11
|
+
}
|
|
12
|
+
get cartAlert() {
|
|
13
|
+
return this.page.locator('.alert-success').first();
|
|
14
|
+
}
|
|
15
|
+
get offcanvasCartItem() {
|
|
16
|
+
return this.page.locator('.offcanvas-cart-items .line-item').first();
|
|
17
|
+
}
|
|
18
|
+
async addToCart() {
|
|
19
|
+
await this.buyButton.click();
|
|
20
|
+
// Wait for offcanvas cart
|
|
21
|
+
await this.page.waitForSelector('.offcanvas.show');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ProductPage = ProductPage;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SearchPage = void 0;
|
|
4
|
+
const BasePage_1 = require("./BasePage");
|
|
5
|
+
class SearchPage extends BasePage_1.BasePage {
|
|
6
|
+
get productItems() {
|
|
7
|
+
return this.page.locator('.cms-element-product-listing .product-box');
|
|
8
|
+
}
|
|
9
|
+
async getFirstProductName() {
|
|
10
|
+
return this.productItems.first().locator('.product-name').textContent();
|
|
11
|
+
}
|
|
12
|
+
async clickFirstProduct() {
|
|
13
|
+
await this.productItems.first().locator('.product-name').click();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.SearchPage = SearchPage;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const HomePage_1 = require("../pages/HomePage");
|
|
5
|
+
const SearchPage_1 = require("../pages/SearchPage");
|
|
6
|
+
const ProductPage_1 = require("../pages/ProductPage");
|
|
7
|
+
const CheckoutPage_1 = require("../pages/CheckoutPage");
|
|
8
|
+
test_1.test.describe('Shopware 6 Storefront Smoke Tests', () => {
|
|
9
|
+
const searchTerm = process.env.SEARCH_TERM || 'Main product';
|
|
10
|
+
(0, test_1.test)('should load the homepage', async ({ page }) => {
|
|
11
|
+
const homePage = new HomePage_1.HomePage(page);
|
|
12
|
+
await homePage.goto();
|
|
13
|
+
await (0, test_1.expect)(homePage.headerLogo).toBeVisible();
|
|
14
|
+
await (0, test_1.expect)(homePage.mainNavigation).toBeVisible();
|
|
15
|
+
await (0, test_1.expect)(homePage.footer).toBeVisible();
|
|
16
|
+
});
|
|
17
|
+
(0, test_1.test)('should search for a product', async ({ page }) => {
|
|
18
|
+
const homePage = new HomePage_1.HomePage(page);
|
|
19
|
+
await homePage.goto();
|
|
20
|
+
await homePage.searchFor(searchTerm);
|
|
21
|
+
const searchPage = new SearchPage_1.SearchPage(page);
|
|
22
|
+
await (0, test_1.expect)(searchPage.productItems.first()).toBeVisible();
|
|
23
|
+
const productName = await searchPage.getFirstProductName();
|
|
24
|
+
(0, test_1.expect)(productName?.toLowerCase()).toContain(searchTerm.toLowerCase());
|
|
25
|
+
});
|
|
26
|
+
(0, test_1.test)('should view product detail page and add to cart', async ({ page }) => {
|
|
27
|
+
const homePage = new HomePage_1.HomePage(page);
|
|
28
|
+
await homePage.goto();
|
|
29
|
+
await homePage.searchFor(searchTerm);
|
|
30
|
+
const searchPage = new SearchPage_1.SearchPage(page);
|
|
31
|
+
await searchPage.clickFirstProduct();
|
|
32
|
+
const productPage = new ProductPage_1.ProductPage(page);
|
|
33
|
+
await (0, test_1.expect)(productPage.productPrice).toBeVisible();
|
|
34
|
+
await (0, test_1.expect)(productPage.buyButton).toBeVisible();
|
|
35
|
+
await productPage.addToCart();
|
|
36
|
+
await (0, test_1.expect)(productPage.cartAlert).toBeVisible();
|
|
37
|
+
await (0, test_1.expect)(productPage.offcanvasCartItem).toBeVisible();
|
|
38
|
+
const checkoutPage = new CheckoutPage_1.CheckoutPage(page);
|
|
39
|
+
await (0, test_1.expect)(checkoutPage.offcanvasCartCheckoutButton).toBeVisible();
|
|
40
|
+
});
|
|
41
|
+
(0, test_1.test)('should reach the checkout confirmation page as a guest', async ({ page }) => {
|
|
42
|
+
// Navigate to a product and add to cart
|
|
43
|
+
const homePage = new HomePage_1.HomePage(page);
|
|
44
|
+
await homePage.goto();
|
|
45
|
+
await homePage.searchFor(searchTerm);
|
|
46
|
+
const searchPage = new SearchPage_1.SearchPage(page);
|
|
47
|
+
await searchPage.clickFirstProduct();
|
|
48
|
+
const productPage = new ProductPage_1.ProductPage(page);
|
|
49
|
+
await productPage.addToCart();
|
|
50
|
+
await (0, test_1.expect)(productPage.cartAlert).toBeVisible();
|
|
51
|
+
await (0, test_1.expect)(productPage.offcanvasCartItem).toBeVisible();
|
|
52
|
+
const checkoutPage = new CheckoutPage_1.CheckoutPage(page);
|
|
53
|
+
await checkoutPage.goToCheckoutFromOffcanvas();
|
|
54
|
+
// Click guest checkout button
|
|
55
|
+
await checkoutPage.guestCheckoutButton.click();
|
|
56
|
+
// Fill guest data
|
|
57
|
+
await checkoutPage.fillGuestData({
|
|
58
|
+
salutation: 'Mr.',
|
|
59
|
+
firstName: 'John',
|
|
60
|
+
lastName: 'Doe',
|
|
61
|
+
email: `test-${Date.now()}@example.com`,
|
|
62
|
+
street: 'Test Street 1',
|
|
63
|
+
zipcode: '12345',
|
|
64
|
+
city: 'Test City',
|
|
65
|
+
country: 'Germany'
|
|
66
|
+
});
|
|
67
|
+
// Verify confirmation page instead of placing order
|
|
68
|
+
await (0, test_1.expect)(checkoutPage.confirmOrderButton).toBeVisible();
|
|
69
|
+
await (0, test_1.expect)(checkoutPage.checkoutAsideSummary).toBeVisible();
|
|
70
|
+
await (0, test_1.expect)(checkoutPage.grandTotal).toBeVisible();
|
|
71
|
+
await (0, test_1.expect)(page).toHaveURL(/.*\/confirm/);
|
|
72
|
+
});
|
|
73
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shopware-e2e-testkit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shopware 6 E2E Smoke Testkit",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shopware-testkit": "./bin/shopware-testkit.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"bin",
|
|
12
|
+
"templates",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "playwright test",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@playwright/test": "^1.59.1",
|
|
25
|
+
"commander": "^14.0.3",
|
|
26
|
+
"dotenv": "^17.4.2",
|
|
27
|
+
"fs-extra": "^11.3.5"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/fs-extra": "^11.0.4",
|
|
31
|
+
"@types/node": "^25.6.2",
|
|
32
|
+
"typescript": "^6.0.3"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Shopware Sales Channel URL
|
|
2
|
+
SALESCHANNEL_URL=https://your-shopware-store.com
|
|
3
|
+
|
|
4
|
+
# (Optional) Search term for product tests
|
|
5
|
+
# SEARCH_TERM=Main product
|
|
6
|
+
|
|
7
|
+
# (Optional) Credentials for guest checkout if needed for specific tests
|
|
8
|
+
# Usually guest checkout doesn't need pre-existing credentials
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read environment variables from file.
|
|
7
|
+
* https://github.com/motdotla/dotenv
|
|
8
|
+
*/
|
|
9
|
+
dotenv.config({ path: path.resolve(process.cwd(), '.env') });
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* See https://playwright.dev/docs/test-configuration.
|
|
13
|
+
*/
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
/* Run tests in files in parallel */
|
|
16
|
+
fullyParallel: true,
|
|
17
|
+
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
18
|
+
forbidOnly: !!process.env.CI,
|
|
19
|
+
/* Retry on CI only */
|
|
20
|
+
retries: process.env.CI ? 2 : 0,
|
|
21
|
+
/* opt out of parallel tests on CI. */
|
|
22
|
+
workers: process.env.CI ? 1 : undefined,
|
|
23
|
+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
24
|
+
reporter: 'html',
|
|
25
|
+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
26
|
+
use: {
|
|
27
|
+
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
28
|
+
baseURL: process.env.SALESCHANNEL_URL || 'http://localhost',
|
|
29
|
+
|
|
30
|
+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
31
|
+
trace: 'on-first-retry',
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/* Configure projects for major browsers */
|
|
35
|
+
projects: [
|
|
36
|
+
{
|
|
37
|
+
name: 'chromium',
|
|
38
|
+
use: { ...devices['Desktop Chrome'] },
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|