cypress-mailisk 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/LICENSE.md +21 -0
- package/README.md +98 -0
- package/index.js +3 -0
- package/package.json +37 -0
- package/src/mailiskCommands.d.ts +104 -0
- package/src/mailiskCommands.js +51 -0
- package/src/register.js +12 -0
- package/src/request.js +86 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Mailisk https://mailisk.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# cypress-mailisk
|
|
2
|
+
|
|
3
|
+
## Install with npm
|
|
4
|
+
|
|
5
|
+
```shell
|
|
6
|
+
npm install -save-dev cypress-mailisk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Install with Yarn
|
|
10
|
+
|
|
11
|
+
```shell
|
|
12
|
+
yarn add cypress-mailisk --dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
After installing the package add the following in your project's `cypress/support/e2e.js`:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import 'cypress-mailisk';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Setup API Key
|
|
22
|
+
|
|
23
|
+
To be able to use the API you will need to add your [API key](http://docs.mailisk.com/#getting-your-api-key) to `cypress.config.js`:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
module.exports = defineConfig({
|
|
27
|
+
env: {
|
|
28
|
+
MAILISK_API_KEY: 'YOUR_API_KEY',
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
The cypress-mailisk plugin provides additional commands which can be accessed on the cypress object, for example `cy.mailiskSearchInbox()`. These commands extend the Chainable object which allows you to use the [`then()`](https://docs.cypress.io/api/commands/then#Usage) method to chain commands.
|
|
36
|
+
|
|
37
|
+
### cy.mailiskSearchInbox
|
|
38
|
+
|
|
39
|
+
This is the main command to interact with Mailisk, it wraps the [Search Inbox](/api-reference/search-inbox) endpoint.
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
cy.mailiskSearchInbox('yournamespace', { to_addr_prefix: 'test.user@' }).then((response) => {
|
|
43
|
+
const emails = response.data;
|
|
44
|
+
// ...
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This Cypress command does a few extra things out of the box compared to calling the raw API directly:
|
|
49
|
+
|
|
50
|
+
- By default it uses the `wait` flag, this means the call won't timeout until at least one email is received or 5 minutes pass. This timeout is adjustable by passing `timeout` in the options array.
|
|
51
|
+
```js
|
|
52
|
+
// timeout of 1 minute
|
|
53
|
+
cy.mailiskSearchInbox(namespace, { timeout: 1000 * 60 });
|
|
54
|
+
// disable wait entirely, may return empty emails objects immediately
|
|
55
|
+
cy.mailiskSearchInbox(namespace, { wait: false });
|
|
56
|
+
```
|
|
57
|
+
- It has a default `from_timestamp` of **current timestmap - 5 seconds**. This means that only new emails will be returned. Without this older emails would be returned, permaturely returning the results if you were waiting for a specific email to arrive.
|
|
58
|
+
|
|
59
|
+
The implementation of these features is explained in the [NodeJS Guide](/guides/nodejs).
|
|
60
|
+
|
|
61
|
+
## Example
|
|
62
|
+
|
|
63
|
+
This example demonstrates going to a password reset page, requesting a new password, receiving reset code link via email and finally setting the new password.
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
describe('Test password reset', () => {
|
|
67
|
+
let resetLink;
|
|
68
|
+
const namespace = 'yournamespace';
|
|
69
|
+
const testEmailAddr = `test.test@${namespace}.mailisk.net`;
|
|
70
|
+
|
|
71
|
+
it('Starts a password reset', () => {
|
|
72
|
+
cy.visit('https://example.com/password_reset');
|
|
73
|
+
cy.get('#email_field').type(testEmailAddr);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('Gets a password reset email', () => {
|
|
77
|
+
cy.mailiskSearchInbox(namespace, {
|
|
78
|
+
to_addr_prefix: testEmailAddr,
|
|
79
|
+
}).then((response) => {
|
|
80
|
+
expect(response.data).to.not.be.empty;
|
|
81
|
+
const email = response.data[0];
|
|
82
|
+
expect(email.subject).to.equal('Please reset your password');
|
|
83
|
+
resetLink = email.text.match(/.(https:\/\/example.com\/password_reset\/.*)>\n*/)[1];
|
|
84
|
+
expect(resetLink).to.not.be.undefined;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('Goes to password reset link', () => {
|
|
89
|
+
cy.visit(resetLink);
|
|
90
|
+
cy.title().should('contain', 'Change your password');
|
|
91
|
+
cy.get('#password').type('MyNewPassword');
|
|
92
|
+
cy.get('#password_confirmation').type('MyNewPassword');
|
|
93
|
+
cy.get('form').submit();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See the full [Mailisk Documentation](https://docs.mailisk.com) for more examples and information.
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cypress-mailisk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Mailisk library for Cypress",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mailisk",
|
|
7
|
+
"cypress",
|
|
8
|
+
"cypress-library",
|
|
9
|
+
"email-automation",
|
|
10
|
+
"email-testing",
|
|
11
|
+
"email"
|
|
12
|
+
],
|
|
13
|
+
"main": "index.js",
|
|
14
|
+
"types": "src/mailiskCommands.d.ts",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"tsc": "node_modules/.bin/tsc src/mailiskCommands.d.ts --types node"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"cypress": ">= 2.1.0"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/mailisk-app/cypress-mailisk.git"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"src"
|
|
27
|
+
],
|
|
28
|
+
"author": "Mailisk",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/mailisk-app/cypress-mailisk/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/mailisk-app/cypress-mailisk#readme",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^4.7.4"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
|
|
3
|
+
export interface EmailAddress {
|
|
4
|
+
/** Email address */
|
|
5
|
+
address: string;
|
|
6
|
+
/** Display name, if one is specified */
|
|
7
|
+
name?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Email {
|
|
11
|
+
/** Namespace scoped ID */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Sender of email */
|
|
14
|
+
from: EmailAddress;
|
|
15
|
+
/** Recepients of email */
|
|
16
|
+
to: EmailAddress[];
|
|
17
|
+
/** Carbon-copied recipients for email message */
|
|
18
|
+
cc?: EmailAddress[];
|
|
19
|
+
/** Blind carbon-copied recipients for email message */
|
|
20
|
+
bcc?: EmailAddress[];
|
|
21
|
+
/** Subject of email */
|
|
22
|
+
subject?: string;
|
|
23
|
+
/** Email content that was sent in HTML format */
|
|
24
|
+
html?: string;
|
|
25
|
+
/** Email content that was sent in plain text format */
|
|
26
|
+
text?: string;
|
|
27
|
+
/** The datetime that this email was received */
|
|
28
|
+
received_date: Date;
|
|
29
|
+
/** The unix timestamp (s) that this email was received */
|
|
30
|
+
received_timestamp: number;
|
|
31
|
+
/** The unix timestamp (s) when this email will be deleted */
|
|
32
|
+
expires_timestamp: number;
|
|
33
|
+
/** The spam score as reported by SpamAssassin */
|
|
34
|
+
spam_score?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SearchInboxParams {
|
|
38
|
+
/**
|
|
39
|
+
* The maximum number of emails that can be returned in this request, used alongside `offset` for pagination.
|
|
40
|
+
*/
|
|
41
|
+
limit?: number;
|
|
42
|
+
/**
|
|
43
|
+
* The number of emails to skip/ignore, used alongside `limit` for pagination.
|
|
44
|
+
*/
|
|
45
|
+
offset?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Filter emails by starting unix timestamp in seconds.
|
|
48
|
+
*/
|
|
49
|
+
from_timestamp?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Filter emails by ending unix timestamp in seconds.
|
|
52
|
+
*/
|
|
53
|
+
to_timestamp?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Filter emails by 'to' address. Address must start with this.
|
|
56
|
+
*
|
|
57
|
+
* 'foo' would return 'foobar@namespace.mailisk.net' but not 'barfoo@namespace.mailisk.net'
|
|
58
|
+
*/
|
|
59
|
+
to_addr_prefix?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Will keep the request going till at least one email would be returned.
|
|
62
|
+
*
|
|
63
|
+
* Default is `true`
|
|
64
|
+
*/
|
|
65
|
+
wait?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SearchInboxResponse {
|
|
69
|
+
/**
|
|
70
|
+
* Total number of emails matching query.
|
|
71
|
+
*/
|
|
72
|
+
total_count: number;
|
|
73
|
+
/**
|
|
74
|
+
* Parameters that were used for the query
|
|
75
|
+
*/
|
|
76
|
+
params: SearchInboxParams;
|
|
77
|
+
/**
|
|
78
|
+
* Emails
|
|
79
|
+
*/
|
|
80
|
+
data: Email[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
declare global {
|
|
84
|
+
namespace Cypress {
|
|
85
|
+
interface Chainable {
|
|
86
|
+
mailiskSearchInbox(
|
|
87
|
+
/**
|
|
88
|
+
* The unique namespace to search.
|
|
89
|
+
*/
|
|
90
|
+
namespace: string,
|
|
91
|
+
/**
|
|
92
|
+
* Search parameters.
|
|
93
|
+
*/
|
|
94
|
+
params?: SearchInboxParams,
|
|
95
|
+
/**
|
|
96
|
+
* Request options.
|
|
97
|
+
*
|
|
98
|
+
* See https://docs.cypress.io/api/commands/request#Arguments
|
|
99
|
+
*/
|
|
100
|
+
options?: Cypress.RequestOptions,
|
|
101
|
+
): Cypress.Chainable<SearchInboxResponse>;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const Request = require('./request');
|
|
2
|
+
|
|
3
|
+
class MailiskCommands {
|
|
4
|
+
static get cypressCommands() {
|
|
5
|
+
return ['mailiskSetApiKey', 'mailiskListNamespaces', 'mailiskSearchInbox'];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
const defaultApiKey = Cypress.env('MAILISK_API_KEY');
|
|
10
|
+
this.mailiskSetApiKey(defaultApiKey);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
mailiskSetApiKey(apiKey) {
|
|
14
|
+
this.request = new Request({ apiKey, baseUrl: Cypress.env('MAILISK_API_URL') });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
mailiskListNamespaces() {
|
|
18
|
+
return this.request.get('/api/namespaces');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
mailiskSearchInbox(namespace, params, options = {}) {
|
|
22
|
+
let _params = { ...params };
|
|
23
|
+
|
|
24
|
+
// default timestamp, 5 seconds before starting this request
|
|
25
|
+
if (!params.from_timestamp) {
|
|
26
|
+
_params.from_timestamp = Math.floor(new Date().getTime() / 1000) - 5;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// by default wait for email
|
|
30
|
+
if (params.wait !== false) {
|
|
31
|
+
_params.wait = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const urlParams = new URLSearchParams();
|
|
35
|
+
for (const key in _params) {
|
|
36
|
+
const value = _params[key];
|
|
37
|
+
if (value) urlParams.set(key, value.toString());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let _options = { ...options };
|
|
41
|
+
|
|
42
|
+
// by default wait 5 minutes for emails
|
|
43
|
+
if (_params.wait && !options.timeout) {
|
|
44
|
+
_options.timeout = 1000 * 60 * 5;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return this.request.get(`api/emails/${namespace}/inbox?${urlParams.toString()}`, _options);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = MailiskCommands;
|
package/src/register.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const MailiskCommands = require('./mailiskCommands');
|
|
2
|
+
|
|
3
|
+
const register = (Cypress) => {
|
|
4
|
+
const mailiskCommands = new MailiskCommands();
|
|
5
|
+
MailiskCommands.cypressCommands.forEach((commandName) => {
|
|
6
|
+
Cypress.Commands.add(commandName, mailiskCommands[commandName].bind(mailiskCommands));
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
register,
|
|
12
|
+
};
|
package/src/request.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const pkg = require('../package.json');
|
|
2
|
+
|
|
3
|
+
class Request {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.apiUrl = options.apiUrl || 'https://api.mailisk.com/';
|
|
6
|
+
this.apiKey = options.apiKey;
|
|
7
|
+
this.headers = {
|
|
8
|
+
Accept: 'application/json',
|
|
9
|
+
'X-Api-Key': `${this.apiKey}`,
|
|
10
|
+
'User-Agent': `cypress-mailisk/${pkg.version}`,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
buildOptions(method, path, opts = {}) {
|
|
15
|
+
if (!this.apiKey) {
|
|
16
|
+
// CYPRESS_ prefix necessary per https://docs.cypress.io/guides/guides/environment-variables.html#Option-3-CYPRESS
|
|
17
|
+
throw new Error('You must set the CYPRESS_MAILISK_API_KEY environment variable to use the Mailisk plugin.');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
method,
|
|
22
|
+
url: `${this.apiUrl}${path}`,
|
|
23
|
+
headers: {
|
|
24
|
+
Accept: this.headers.Accept,
|
|
25
|
+
'X-Api-Key': this.headers['X-Api-Key'],
|
|
26
|
+
'User-Agent': this.headers['User-Agent'],
|
|
27
|
+
},
|
|
28
|
+
...opts,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getResponseHandler(includeResponseMetadata = false) {
|
|
33
|
+
return (response) => {
|
|
34
|
+
if (response.isOkStatusCode) {
|
|
35
|
+
return includeResponseMetadata ? response : response.body;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let message = '';
|
|
39
|
+
switch (response.status) {
|
|
40
|
+
case 400:
|
|
41
|
+
try {
|
|
42
|
+
const json = response.body;
|
|
43
|
+
json.errors.forEach((err) => {
|
|
44
|
+
message += `(${err.field}) ${err.detail[0].description}\r\n`;
|
|
45
|
+
});
|
|
46
|
+
} catch (e) {
|
|
47
|
+
message = 'Request had one or more invalid parameters.';
|
|
48
|
+
}
|
|
49
|
+
throw new Error(message);
|
|
50
|
+
case 401:
|
|
51
|
+
throw new Error('Authentication failed, check your API key.');
|
|
52
|
+
case 403:
|
|
53
|
+
throw new Error('Insufficient permission to perform that task.');
|
|
54
|
+
case 404:
|
|
55
|
+
throw new Error('Not found, check input parameters.');
|
|
56
|
+
default:
|
|
57
|
+
throw new Error('API error occured.');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
request(method, path, body, opts = {}) {
|
|
63
|
+
const options = this.buildOptions(method, path, opts);
|
|
64
|
+
options.body = body || undefined;
|
|
65
|
+
options.failOnStatusCode = false;
|
|
66
|
+
return cy.request(options).then(this.getResponseHandler());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get(path, opts) {
|
|
70
|
+
return this.request('GET', path, undefined, opts);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
post(path, body, opts) {
|
|
74
|
+
return this.request('POST', path, body, opts);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
put(path, body, opts) {
|
|
78
|
+
return this.request('PUT', path, body, opts);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
del(path, opts) {
|
|
82
|
+
return this.request('DELETE', path, undefined, opts);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = Request;
|