@shopware-ag/acceptance-test-suite 2.3.10 → 2.4.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 +30 -1
- package/dist/index.d.mts +53 -1
- package/dist/index.d.ts +53 -1
- package/dist/index.mjs +132 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ This test suite is an extension to [Playwright](https://playwright.dev/) to easi
|
|
|
15
15
|
* [Actor Pattern](#actor-pattern)
|
|
16
16
|
* [Data Fixtures](#data-fixtures)
|
|
17
17
|
* [Code Contribution](#code-contribution)
|
|
18
|
+
* [Best practices](#best-practices)
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
Start by creating your own [Playwright](https://playwright.dev/docs/intro) project.
|
|
@@ -374,4 +375,32 @@ If you create your own data fixtures make sure to import and merge them in your
|
|
|
374
375
|
## Code Contribution
|
|
375
376
|
You can contribute to this project via its [official repository](https://github.com/shopware/acceptance-test-suite/) on GitHub.
|
|
376
377
|
|
|
377
|
-
This project uses [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). Please make sure to form your commits accordingly to the spec.
|
|
378
|
+
This project uses [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). Please make sure to form your commits accordingly to the spec.
|
|
379
|
+
|
|
380
|
+
## Best practices
|
|
381
|
+
|
|
382
|
+
A good first read about this is the official [playwright best practices page](https://playwright.dev/docs/best-practices). It describes the most important practices which should also be followed when writing acceptance tests for Shopware.
|
|
383
|
+
|
|
384
|
+
The most important part is [test isolation](https://playwright.dev/docs/best-practices#make-tests-as-isolated-as-possible) which helps to prevent flaky behavior and enables the test to be run in parallel and on systems with an unknown state.
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
### Dos
|
|
388
|
+
|
|
389
|
+
- use fixtures or the [`TestDataService`](./src/services/TestDataService.ts)
|
|
390
|
+
- create all the data that is required for your test case. That includes sales channels, customers and users (the page fixtures handle most of the common use cases)
|
|
391
|
+
- if you need specific settings for your test, set it explicitly for the user/customer/sales channel
|
|
392
|
+
- directly jump to detail pages with the id of the entities you've created
|
|
393
|
+
- if that's no possible, use the search with a unique name to filter lists to just that single entity
|
|
394
|
+
|
|
395
|
+
### Don'ts
|
|
396
|
+
|
|
397
|
+
- do not expect lists/tables to only contain one item, leverage unique ids/names to open or find your entity instead
|
|
398
|
+
- same with helper functions, do not except to only get item back from the API. Always a unique criteria to the API call
|
|
399
|
+
- avoid unused fixtures: if you request a fixture but don't use any data from the fixture, the test or fixture should be refactored
|
|
400
|
+
- do not depend on implicit configuration and existing data. Examples:
|
|
401
|
+
- rules
|
|
402
|
+
- flows
|
|
403
|
+
- categories
|
|
404
|
+
- do not expect the shop to have the defaults en_GB and EUR
|
|
405
|
+
- do not change global settings (sales channel is ok, because it's created by us)
|
|
406
|
+
- basically everything in Settings that is not specific to a sales channel (tax, search, etc.)
|
package/dist/index.d.mts
CHANGED
|
@@ -70,9 +70,61 @@ declare class StoreApiContext {
|
|
|
70
70
|
head<PAYLOAD>(url: string, options?: RequestOptions<PAYLOAD>): Promise<APIResponse>;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
interface Email {
|
|
74
|
+
fromName: string;
|
|
75
|
+
fromAddress: string;
|
|
76
|
+
toName: string;
|
|
77
|
+
toAddress: string;
|
|
78
|
+
subject: string;
|
|
79
|
+
emailId: string;
|
|
80
|
+
}
|
|
81
|
+
declare class MailpitApiContext {
|
|
82
|
+
context: APIRequestContext;
|
|
83
|
+
constructor(context: APIRequestContext);
|
|
84
|
+
/**
|
|
85
|
+
* Fetches email headers based on the recipient's email address.
|
|
86
|
+
* @param email - The email address of the recipient.
|
|
87
|
+
* @returns An Email object containing the email headers.
|
|
88
|
+
*/
|
|
89
|
+
getEmailHeaders(email: string): Promise<Email>;
|
|
90
|
+
/**
|
|
91
|
+
* Retrieves the body content of the latest email as an HTML string.
|
|
92
|
+
* @returns The HTML content of the latest email.
|
|
93
|
+
*/
|
|
94
|
+
getEmailBody(): Promise<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Generates the full email content, combining headers and body.
|
|
97
|
+
* @param email - The email address to fetch headers for.
|
|
98
|
+
* @returns The full email content as a string.
|
|
99
|
+
*/
|
|
100
|
+
generateEmailContent(email: string): Promise<string>;
|
|
101
|
+
/**
|
|
102
|
+
* Retrieves the plain text content of the latest email.
|
|
103
|
+
* @returns The plain text content of the latest email.
|
|
104
|
+
*/
|
|
105
|
+
getRenderMessageTxt(): Promise<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Extracts the first URL found in the plain text content of the latest email.
|
|
108
|
+
* @returns The first URL found in the email content.
|
|
109
|
+
* @throws An error if no URL is found in the email content.
|
|
110
|
+
*/
|
|
111
|
+
getLinkFromMail(): Promise<string>;
|
|
112
|
+
/**
|
|
113
|
+
* Deletes a specific email by ID if provided, or deletes all emails if no ID is provided.
|
|
114
|
+
* @param emailId - The ID of the email to delete (optional).
|
|
115
|
+
*/
|
|
116
|
+
deleteMail(emailId?: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Creates a new EmailApiContext instance with the appropriate configuration.
|
|
119
|
+
* @returns A promise that resolves to an EmailApiContext instance.
|
|
120
|
+
*/
|
|
121
|
+
static create(baseURL: string): Promise<MailpitApiContext>;
|
|
122
|
+
}
|
|
123
|
+
|
|
73
124
|
interface ApiContextTypes {
|
|
74
125
|
AdminApiContext: AdminApiContext;
|
|
75
126
|
StoreApiContext: StoreApiContext;
|
|
127
|
+
MailpitApiContext: MailpitApiContext;
|
|
76
128
|
}
|
|
77
129
|
|
|
78
130
|
interface PageContextTypes {
|
|
@@ -460,7 +512,7 @@ declare class TestDataService {
|
|
|
460
512
|
/**
|
|
461
513
|
* Will delete all entities created by the data service via sync API.
|
|
462
514
|
*/
|
|
463
|
-
cleanUp(): Promise<playwright_core.APIResponse>;
|
|
515
|
+
cleanUp(): Promise<playwright_core.APIResponse | null>;
|
|
464
516
|
/**
|
|
465
517
|
* Convert a JS date object into a date-time compatible string.
|
|
466
518
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -70,9 +70,61 @@ declare class StoreApiContext {
|
|
|
70
70
|
head<PAYLOAD>(url: string, options?: RequestOptions<PAYLOAD>): Promise<APIResponse>;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
interface Email {
|
|
74
|
+
fromName: string;
|
|
75
|
+
fromAddress: string;
|
|
76
|
+
toName: string;
|
|
77
|
+
toAddress: string;
|
|
78
|
+
subject: string;
|
|
79
|
+
emailId: string;
|
|
80
|
+
}
|
|
81
|
+
declare class MailpitApiContext {
|
|
82
|
+
context: APIRequestContext;
|
|
83
|
+
constructor(context: APIRequestContext);
|
|
84
|
+
/**
|
|
85
|
+
* Fetches email headers based on the recipient's email address.
|
|
86
|
+
* @param email - The email address of the recipient.
|
|
87
|
+
* @returns An Email object containing the email headers.
|
|
88
|
+
*/
|
|
89
|
+
getEmailHeaders(email: string): Promise<Email>;
|
|
90
|
+
/**
|
|
91
|
+
* Retrieves the body content of the latest email as an HTML string.
|
|
92
|
+
* @returns The HTML content of the latest email.
|
|
93
|
+
*/
|
|
94
|
+
getEmailBody(): Promise<string>;
|
|
95
|
+
/**
|
|
96
|
+
* Generates the full email content, combining headers and body.
|
|
97
|
+
* @param email - The email address to fetch headers for.
|
|
98
|
+
* @returns The full email content as a string.
|
|
99
|
+
*/
|
|
100
|
+
generateEmailContent(email: string): Promise<string>;
|
|
101
|
+
/**
|
|
102
|
+
* Retrieves the plain text content of the latest email.
|
|
103
|
+
* @returns The plain text content of the latest email.
|
|
104
|
+
*/
|
|
105
|
+
getRenderMessageTxt(): Promise<string>;
|
|
106
|
+
/**
|
|
107
|
+
* Extracts the first URL found in the plain text content of the latest email.
|
|
108
|
+
* @returns The first URL found in the email content.
|
|
109
|
+
* @throws An error if no URL is found in the email content.
|
|
110
|
+
*/
|
|
111
|
+
getLinkFromMail(): Promise<string>;
|
|
112
|
+
/**
|
|
113
|
+
* Deletes a specific email by ID if provided, or deletes all emails if no ID is provided.
|
|
114
|
+
* @param emailId - The ID of the email to delete (optional).
|
|
115
|
+
*/
|
|
116
|
+
deleteMail(emailId?: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Creates a new EmailApiContext instance with the appropriate configuration.
|
|
119
|
+
* @returns A promise that resolves to an EmailApiContext instance.
|
|
120
|
+
*/
|
|
121
|
+
static create(baseURL: string): Promise<MailpitApiContext>;
|
|
122
|
+
}
|
|
123
|
+
|
|
73
124
|
interface ApiContextTypes {
|
|
74
125
|
AdminApiContext: AdminApiContext;
|
|
75
126
|
StoreApiContext: StoreApiContext;
|
|
127
|
+
MailpitApiContext: MailpitApiContext;
|
|
76
128
|
}
|
|
77
129
|
|
|
78
130
|
interface PageContextTypes {
|
|
@@ -460,7 +512,7 @@ declare class TestDataService {
|
|
|
460
512
|
/**
|
|
461
513
|
* Will delete all entities created by the data service via sync API.
|
|
462
514
|
*/
|
|
463
|
-
cleanUp(): Promise<playwright_core.APIResponse>;
|
|
515
|
+
cleanUp(): Promise<playwright_core.APIResponse | null>;
|
|
464
516
|
/**
|
|
465
517
|
* Convert a JS date object into a date-time compatible string.
|
|
466
518
|
*
|
package/dist/index.mjs
CHANGED
|
@@ -393,16 +393,16 @@ const test$b = test$d.extend({
|
|
|
393
393
|
]
|
|
394
394
|
});
|
|
395
395
|
|
|
396
|
-
var __defProp$
|
|
397
|
-
var __defNormalProp$
|
|
398
|
-
var __publicField$
|
|
399
|
-
__defNormalProp$
|
|
396
|
+
var __defProp$t = Object.defineProperty;
|
|
397
|
+
var __defNormalProp$t = (obj, key, value) => key in obj ? __defProp$t(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
398
|
+
var __publicField$t = (obj, key, value) => {
|
|
399
|
+
__defNormalProp$t(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
400
400
|
return value;
|
|
401
401
|
};
|
|
402
402
|
const _AdminApiContext = class _AdminApiContext {
|
|
403
403
|
constructor(context, options) {
|
|
404
|
-
__publicField$
|
|
405
|
-
__publicField$
|
|
404
|
+
__publicField$t(this, "context");
|
|
405
|
+
__publicField$t(this, "options");
|
|
406
406
|
this.context = context;
|
|
407
407
|
this.options = options;
|
|
408
408
|
}
|
|
@@ -494,7 +494,7 @@ const _AdminApiContext = class _AdminApiContext {
|
|
|
494
494
|
return this.context.head(url, options);
|
|
495
495
|
}
|
|
496
496
|
};
|
|
497
|
-
__publicField$
|
|
497
|
+
__publicField$t(_AdminApiContext, "defaultOptions", {
|
|
498
498
|
app_url: process.env["APP_URL"],
|
|
499
499
|
client_id: process.env["SHOPWARE_ACCESS_KEY_ID"],
|
|
500
500
|
client_secret: process.env["SHOPWARE_SECRET_ACCESS_KEY"],
|
|
@@ -504,16 +504,16 @@ __publicField$s(_AdminApiContext, "defaultOptions", {
|
|
|
504
504
|
});
|
|
505
505
|
let AdminApiContext = _AdminApiContext;
|
|
506
506
|
|
|
507
|
-
var __defProp$
|
|
508
|
-
var __defNormalProp$
|
|
509
|
-
var __publicField$
|
|
510
|
-
__defNormalProp$
|
|
507
|
+
var __defProp$s = Object.defineProperty;
|
|
508
|
+
var __defNormalProp$s = (obj, key, value) => key in obj ? __defProp$s(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
509
|
+
var __publicField$s = (obj, key, value) => {
|
|
510
|
+
__defNormalProp$s(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
511
511
|
return value;
|
|
512
512
|
};
|
|
513
513
|
const _StoreApiContext = class _StoreApiContext {
|
|
514
514
|
constructor(context, options) {
|
|
515
|
-
__publicField$
|
|
516
|
-
__publicField$
|
|
515
|
+
__publicField$s(this, "context");
|
|
516
|
+
__publicField$s(this, "options");
|
|
517
517
|
this.context = context;
|
|
518
518
|
this.options = options;
|
|
519
519
|
}
|
|
@@ -572,12 +572,122 @@ const _StoreApiContext = class _StoreApiContext {
|
|
|
572
572
|
return this.context.head(url, options);
|
|
573
573
|
}
|
|
574
574
|
};
|
|
575
|
-
__publicField$
|
|
575
|
+
__publicField$s(_StoreApiContext, "defaultOptions", {
|
|
576
576
|
app_url: process.env["APP_URL"],
|
|
577
577
|
ignoreHTTPSErrors: true
|
|
578
578
|
});
|
|
579
579
|
let StoreApiContext = _StoreApiContext;
|
|
580
580
|
|
|
581
|
+
var __defProp$r = Object.defineProperty;
|
|
582
|
+
var __defNormalProp$r = (obj, key, value) => key in obj ? __defProp$r(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
583
|
+
var __publicField$r = (obj, key, value) => {
|
|
584
|
+
__defNormalProp$r(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
585
|
+
return value;
|
|
586
|
+
};
|
|
587
|
+
class MailpitApiContext {
|
|
588
|
+
constructor(context) {
|
|
589
|
+
__publicField$r(this, "context");
|
|
590
|
+
this.context = context;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Fetches email headers based on the recipient's email address.
|
|
594
|
+
* @param email - The email address of the recipient.
|
|
595
|
+
* @returns An Email object containing the email headers.
|
|
596
|
+
*/
|
|
597
|
+
async getEmailHeaders(email) {
|
|
598
|
+
const response = await this.context.get("/api/v1/search", {
|
|
599
|
+
params: {
|
|
600
|
+
kind: "To.Address",
|
|
601
|
+
query: email
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
const responseJson = await response.json();
|
|
605
|
+
return {
|
|
606
|
+
fromName: responseJson.messages[0].From.Name,
|
|
607
|
+
fromAddress: responseJson.messages[0].From.Address,
|
|
608
|
+
toName: responseJson.messages[0].To[0].Name,
|
|
609
|
+
toAddress: responseJson.messages[0].To[0].Address,
|
|
610
|
+
subject: responseJson.messages[0].Subject,
|
|
611
|
+
emailId: responseJson.messages[0].ID
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Retrieves the body content of the latest email as an HTML string.
|
|
616
|
+
* @returns The HTML content of the latest email.
|
|
617
|
+
*/
|
|
618
|
+
async getEmailBody() {
|
|
619
|
+
const response = await this.context.get("view/latest.html");
|
|
620
|
+
const buffer = await response.body();
|
|
621
|
+
const htmlString = buffer.toString("utf-8");
|
|
622
|
+
return htmlString;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Generates the full email content, combining headers and body.
|
|
626
|
+
* @param email - The email address to fetch headers for.
|
|
627
|
+
* @returns The full email content as a string.
|
|
628
|
+
*/
|
|
629
|
+
async generateEmailContent(email) {
|
|
630
|
+
const headers = await this.getEmailHeaders(email);
|
|
631
|
+
const htmlTemplate = await this.getEmailBody();
|
|
632
|
+
const headerSection = `
|
|
633
|
+
<div style="font-family:arial; font-size:16px;" id="email-container">
|
|
634
|
+
<p id="from"><strong>From:</strong> ${headers.fromName} <${headers.fromAddress}></p>
|
|
635
|
+
<p id="to"><strong>To:</strong> ${headers.toName} <${headers.toAddress}></p>
|
|
636
|
+
<p id="subject"><strong>Subject:</strong> ${headers.subject}</p>
|
|
637
|
+
</div>
|
|
638
|
+
`;
|
|
639
|
+
const emailContent = headerSection + htmlTemplate;
|
|
640
|
+
return emailContent;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Retrieves the plain text content of the latest email.
|
|
644
|
+
* @returns The plain text content of the latest email.
|
|
645
|
+
*/
|
|
646
|
+
async getRenderMessageTxt() {
|
|
647
|
+
const response = await this.context.get("view/latest.txt");
|
|
648
|
+
const buffer = await response.body();
|
|
649
|
+
const text = buffer.toString("utf-8");
|
|
650
|
+
return text;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Extracts the first URL found in the plain text content of the latest email.
|
|
654
|
+
* @returns The first URL found in the email content.
|
|
655
|
+
* @throws An error if no URL is found in the email content.
|
|
656
|
+
*/
|
|
657
|
+
async getLinkFromMail() {
|
|
658
|
+
const textContent = await this.getRenderMessageTxt();
|
|
659
|
+
const urlMatch = textContent.match(/https?:\/\/[^\s]+/);
|
|
660
|
+
if (urlMatch && urlMatch.length > 0) {
|
|
661
|
+
return urlMatch[0];
|
|
662
|
+
}
|
|
663
|
+
throw new Error("No URL found in the email content");
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Deletes a specific email by ID if provided, or deletes all emails if no ID is provided.
|
|
667
|
+
* @param emailId - The ID of the email to delete (optional).
|
|
668
|
+
*/
|
|
669
|
+
async deleteMail(emailId) {
|
|
670
|
+
const data = emailId ? { IDs: [emailId] } : {};
|
|
671
|
+
await this.context.delete(`api/v1/messages`, { data });
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Creates a new EmailApiContext instance with the appropriate configuration.
|
|
675
|
+
* @returns A promise that resolves to an EmailApiContext instance.
|
|
676
|
+
*/
|
|
677
|
+
static async create(baseURL) {
|
|
678
|
+
const extraHTTPHeaders = {
|
|
679
|
+
"Accept": "application/json",
|
|
680
|
+
"Content-Type": "application/json"
|
|
681
|
+
};
|
|
682
|
+
const context = await request.newContext({
|
|
683
|
+
baseURL,
|
|
684
|
+
ignoreHTTPSErrors: true,
|
|
685
|
+
extraHTTPHeaders
|
|
686
|
+
});
|
|
687
|
+
return new MailpitApiContext(context);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
581
691
|
const test$a = test$d.extend({
|
|
582
692
|
AdminApiContext: [
|
|
583
693
|
async ({}, use) => {
|
|
@@ -597,6 +707,13 @@ const test$a = test$d.extend({
|
|
|
597
707
|
await use(storeApiContext);
|
|
598
708
|
},
|
|
599
709
|
{ scope: "worker" }
|
|
710
|
+
],
|
|
711
|
+
MailpitApiContext: [
|
|
712
|
+
async ({}, use) => {
|
|
713
|
+
const mailpitApiContext = await MailpitApiContext.create(process.env["MAILPIT_BASE_URL"]);
|
|
714
|
+
await use(mailpitApiContext);
|
|
715
|
+
},
|
|
716
|
+
{ scope: "worker" }
|
|
600
717
|
]
|
|
601
718
|
});
|
|
602
719
|
|
|
@@ -1400,7 +1517,7 @@ class TestDataService {
|
|
|
1400
1517
|
*/
|
|
1401
1518
|
async cleanUp() {
|
|
1402
1519
|
if (!this.shouldCleanUp) {
|
|
1403
|
-
return
|
|
1520
|
+
return null;
|
|
1404
1521
|
}
|
|
1405
1522
|
const priorityDeleteOperations = {};
|
|
1406
1523
|
const deleteOperations = {};
|