mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 14:19:58 +00:00
Chore: replace spectron with playwright (#4305)
* add smoke test fixture * respect INSOMNIA_DATA_PATH override in CI * add playwright * remove spectron * move CLI tests after app smoke tests in CI * remove onboarding skip * random path feedback * npx feedback * remove DATA_PATH override * remove step from import process * cleanup * restore readme * move specs to tests * feedback on DESIGNER_DATA_PATH * remove skipLibCheck * last mention of spectron * fix windows npm run test:smoke:build * DATA_PATH override is required * github CI is slow sometimes
This commit is contained in:
parent
b2c94ebbdb
commit
8cea5edc26
13
.github/workflows/test.yml
vendored
13
.github/workflows/test.yml
vendored
@ -61,6 +61,12 @@ jobs:
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build app for smoke tests
|
||||
run: npm run app-build:smoke
|
||||
|
||||
- name: Run app smoke tests
|
||||
run: npm run test:smoke:build
|
||||
|
||||
- name: Set Inso CLI variables
|
||||
id: inso-variables
|
||||
shell: bash
|
||||
@ -133,13 +139,6 @@ jobs:
|
||||
- name: Run Inso CLI smoke tests
|
||||
run: npm run test:smoke:cli
|
||||
|
||||
- name: Build app for smoke tests
|
||||
run: npm run app-build:smoke
|
||||
|
||||
- name: Run app smoke tests
|
||||
timeout-minutes: 10 # sometimes jest fails to exit - https://github.com/facebook/jest/issues/6423#issuecomment-620407580
|
||||
run: npm run test:smoke:build
|
||||
|
||||
- name: Upload smoke test screenshots
|
||||
uses: actions/upload-artifact@v2
|
||||
if: always()
|
||||
|
@ -57,7 +57,7 @@ Insomnia stores data in a few places:
|
||||
## Automated testing
|
||||
|
||||
We use [Jest](https://jestjs.io/) and [react-testing-library](https://testing-library.com/docs/react-testing-library)
|
||||
to write our unit tests, and [Spectron](https://www.electronjs.org/spectron) for integration tests.
|
||||
to write our unit tests, and [Playwright](https://github.com/microsoft/playwright) for integration tests.
|
||||
|
||||
Unit tests exist alongside the file under test. For example:
|
||||
|
||||
|
@ -37,7 +37,7 @@ log.info(`Running version ${getAppVersion()}`);
|
||||
if (!isDevelopment()) {
|
||||
const defaultPath = app.getPath('userData');
|
||||
const newPath = path.join(defaultPath, '../', appConfig.userDataFolder);
|
||||
app.setPath('userData', newPath);
|
||||
app.setPath('userData', process.env.INSOMNIA_DATA_PATH ?? newPath);
|
||||
}
|
||||
|
||||
// So if (window) checks don't throw
|
||||
@ -145,11 +145,14 @@ function _launchApp() {
|
||||
commandLineArgs.length && window.send('run-command', commandLineArgs[0]);
|
||||
});
|
||||
// Called when second instance launched with args (Windows)
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
console.error('[app] Failed to get instance lock');
|
||||
return;
|
||||
// @TODO: Investigate why this closes electron when using playwright (tested on macOS)
|
||||
// and find a better solution.
|
||||
if (!process.env.PLAYWRIGHT) {
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
console.error('[app] Failed to get instance lock');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
app.on('second-instance', () => {
|
||||
|
1
packages/insomnia-smoke-test/.npmrc
Normal file
1
packages/insomnia-smoke-test/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
playwright_skip_browser_download=true
|
@ -2,17 +2,16 @@
|
||||
|
||||
This project contains the smoke testing suite for Insomnia and Inso.
|
||||
|
||||
Tests for the Electron app are written using [Spectron](https://github.com/electron-userland/spectron#application-api) (and [spectron-keys](https://github.com/jsantell/spectron-keys) for key inputs), while tests for the CLI use [execa](https://github.com/sindresorhus/execa).
|
||||
Tests for the Electron app are written using [Playwright](https://github.com/microsoft/playwright) while tests for the CLI use [execa](https://github.com/sindresorhus/execa).
|
||||
|
||||
## Structure
|
||||
|
||||
| Folder | Purpose |
|
||||
| - | - |
|
||||
| `/cli` | tests for inso |
|
||||
| `/core` | tests for Insomnia |
|
||||
| `/server` | Express server used by the tests |
|
||||
| Folder | Purpose |
|
||||
| ----------- | --------------------------------- |
|
||||
| `/cli` | tests for inso |
|
||||
| `/tests` | tests for Insomnia |
|
||||
| `/server` | Express server used by the tests |
|
||||
| `/fixtures` | data used by tests and the server |
|
||||
| `/modules` | logical grouping of functionality (eg. `modals` , `tabs` , `settings` , `home` ) |
|
||||
|
||||
## How to run
|
||||
|
||||
@ -29,8 +28,10 @@ npm run inso-package # Package the Inso CLI binaries
|
||||
You can then run the smoke tests, again from the root:
|
||||
|
||||
```shell
|
||||
npm run test:smoke:cli # Run CLI tests
|
||||
npm run test:smoke:build # Run Insomnia tests
|
||||
npm run test:smoke:cli # Run CLI tests
|
||||
npm run test:smoke:build # Run Insomnia tests
|
||||
PWDEBUG=1 npm run test:smoke:build # Write Insomnia tests with the playwrite recorder
|
||||
DEBUG=pw:browser,pw:api npm run test:smoke:build # Run Insomnia tests, with verbose output
|
||||
```
|
||||
|
||||
Sometimes, you might need to run tests against a _packaged_ application. A packaged application is the final artifact which bundles all of the various resources together, and is created for distribution in the form of a `.dmg` or `.exe`, etc. Packaging takes longer to do and is only required for edge cases (such as a <!-- TODO(TSCONVERSION) update this link -->[plugin installation](https://github.com/Kong/insomnia/blob/357b8f05f89fd5c07a75d8418670abe37b2882dc/packages/insomnia-smoke-test/designer/app.test.js#L36)), so we typically run tests against a build. To run packaged tests, from the root:
|
||||
@ -46,7 +47,7 @@ Each of the above commands will automatically run the Express server, so you do
|
||||
|
||||
When writing tests, it is recommended to use the scripts in this project directly (instead of from the root, as per the section above). After building and/or packaging your application under test, it will be available under `packages/insomnia-app/{build|dist}` and you can begin writing your test.
|
||||
|
||||
In order to run tests for development, open two terminal tabs in `packages/insomnia-smoke-test`:
|
||||
In order to run CLI tests for development, open two terminal tabs in `packages/insomnia-smoke-test`:
|
||||
|
||||
```shell
|
||||
# In the first tab, serve the Express API
|
||||
@ -54,15 +55,10 @@ npm run serve
|
||||
|
||||
# In the second tab, run your tests
|
||||
npm run cli # Run CLI tests
|
||||
npm run spectron:build # Insomnia build tests
|
||||
|
||||
npm run spectron:package # Insomnia package tests
|
||||
```
|
||||
|
||||
This will allow you to write and monitor the server separately from each test, speeding up the development cycle.
|
||||
|
||||
You may also need to run a test multiple times. You can focus a particular test or test suite using [Jest globals](https://jestjs.io/docs/en/api#testonlyname-fn-timeout) such as `it.only` and `describe.skip`, or their aliases `fit` and `xdescribe`, etc.
|
||||
|
||||
## General guidelines
|
||||
|
||||
### Data
|
||||
@ -73,139 +69,6 @@ Individual tests will automatically run against a clean Insomnia data directory
|
||||
|
||||
A test should not depend on any external services unless absolutely necessary. If a particular endpoint is required (eg. for authentication or a specific content type), implement a new endpoint in `/server`.
|
||||
|
||||
### Element selection
|
||||
|
||||
Spectron is built heavily on top of WebdriverIO, and WebdriverIO's `browser` object is available under `app.client` ([docs](https://github.com/electron-userland/spectron#client)). This is the primary API you will need for user interactions, see examples with existing tests.
|
||||
|
||||
Through WebdriverIO you can use a host of CSS or React selectors. There is no clear guideline about which selector to use, but whichever approach is used it must favour stability and be understandable.
|
||||
|
||||
There are trade-offs with each selector approach but it's important to know how generic or specific a particular component or CSS class is, in order to ensure that the correct element is always selected as the application evolves.
|
||||
|
||||
#### Select by component and props
|
||||
|
||||
Sometimes selecting by a React component and props, directly from `app.client` is the cleanest approach, as the following two examples show:
|
||||
|
||||
```ts
|
||||
const waitUntilRequestIsActive = async (app: Application, name: string) => {
|
||||
const request = await app.client.react$('UnconnectedSidebarRequestRow', {
|
||||
props: { isActive: true, request: { name } },
|
||||
});
|
||||
|
||||
await request.waitForDisplayed();
|
||||
};
|
||||
|
||||
export const clickFolderByName = async (app, name) => {
|
||||
const folder = await app.client.react$('UnconnectedSidebarRequestGroupRow', {
|
||||
props: { requestGroup: { name } },
|
||||
});
|
||||
|
||||
await folder.waitForClickable();
|
||||
await folder.click();
|
||||
};
|
||||
```
|
||||
|
||||
You can find a list of component names in `modules/component-names.ts`.
|
||||
|
||||
#### Scoping
|
||||
|
||||
It is important to scope an element to an appropriate ancestor. In a way the selector becomes self-documenting, but also ensures stability as the UI evolves.
|
||||
|
||||
In the following example, it is possible for multiple buttons which match the `button#enabled` selector to exist on the page. By chaining a React and CSS selector, we can ensure the test runner will always click the expected button within the `BasicAuth` component.
|
||||
|
||||
```ts
|
||||
export const toggleBasicAuthEnabled = async (app: Application) => {
|
||||
await app.client
|
||||
.react$('BasicAuth')
|
||||
.then(e => e.$('button#enabled'))
|
||||
.then(e => e.click());
|
||||
};
|
||||
```
|
||||
|
||||
A similar approach can be achieved through a CSS selector. In the following example, after sending a successful request, we want to detect an element containing the CSS classes `tag bg-success` and ensure it contains the text `200 OK`.
|
||||
|
||||
These classes are fairly generic and could exist multiple times on the page, but the HTTP response code will always be in the response pane (`response-pane`) header (`pane__header`). As such, the selector is scoped to always select the expected element, wait for it to show, and ensure it has the expected text.
|
||||
|
||||
```ts
|
||||
export const expect200 = async (app: Application) => {
|
||||
const tag = await app.client.$('.response-pane .pane__header .tag.bg-success');
|
||||
await tag.waitForDisplayed();
|
||||
await expectText(tag, '200 OK');
|
||||
};
|
||||
```
|
||||
|
||||
### Interactions
|
||||
|
||||
As is common with all smoke testing frameworks, before interacting with an element (click, hover, etc) it is generally good to check whether you _can_ interact with it. For instance, clicking a button will fail if the button is not yet clickable.
|
||||
|
||||
Sometimes you will need to add explicit pauses to allow for UI to refresh or database writes to occur (`await app.client.pause(500)`). Try to keep these to a minimum, though, exploring all other avenues first, such as WebdriverIO's `waitFor*` functions. Avoiding explicit waits ensures each test runs in the short amount of time.
|
||||
|
||||
When typing in the url bar for HTTP requests, we first wait for it to exist on the page before clicking on it and typing, because request activation can take some time.
|
||||
|
||||
```ts
|
||||
export const typeInUrlBar = async (app: Application, url: string) => {
|
||||
const urlEditor = await app.client.react$('RequestUrlBar');
|
||||
await urlEditor.waitForExist();
|
||||
await urlEditor.click();
|
||||
await urlEditor.keys(url);
|
||||
};
|
||||
```
|
||||
|
||||
In addition, sometimes we want to wait for an element to hide instead of show. To achieve this, we can use the `reverse` option available through WebdriverIO, as shown in the following example.
|
||||
|
||||
```ts
|
||||
// Wait for spinner to show
|
||||
const spinner = await app.client.react$('ResponseTimer');
|
||||
await spinner.waitForDisplayed();
|
||||
|
||||
// Wait for spinner to hide
|
||||
await spinner.waitForDisplayed({ reverse: true });
|
||||
```
|
||||
|
||||
### Readability
|
||||
|
||||
It is important for a smoke test to be _readable_ so the flow can be understood, and the (often complicated) implementation details hidden, like in the example below.
|
||||
|
||||
```ts
|
||||
import * as debug from '../modules/debug';
|
||||
|
||||
it('sends request with basic authentication', async () => {
|
||||
const url = 'http://127.0.0.1:4010/auth/basic';
|
||||
const { latin1, utf8 } = basicAuthCreds;
|
||||
|
||||
await debug.workspaceDropdownExists(app);
|
||||
await debug.createNewRequest(app, 'basic-auth');
|
||||
await debug.typeInUrlBar(app, url);
|
||||
|
||||
// Send request with no auth present
|
||||
await debug.clickSendRequest(app);
|
||||
await debug.expect401(app);
|
||||
|
||||
// Click auth tab
|
||||
await debug.clickRequestAuthTab(app);
|
||||
await debug.expectNoAuthSelected(app);
|
||||
|
||||
// Select basic auth
|
||||
await debug.clickRequestAuthDropdown(app);
|
||||
await debug.clickBasicAuth(app);
|
||||
|
||||
// Enter username and password with regular characters
|
||||
await debug.typeBasicAuthUsernameAndPassword(app, utf8.raw.user, utf8.raw.pass);
|
||||
|
||||
// Send request with auth present
|
||||
await debug.clickSendRequest(app);
|
||||
await debug.expect200(app);
|
||||
|
||||
const responseViewer = await debug.getResponseViewer(app);
|
||||
await debug.expectText(responseViewer, '1\nbasic auth received');
|
||||
});
|
||||
```
|
||||
|
||||
In most cases, it will be beneficial to create helper functions under `/modules`, regardless of how reusable they are. Some modules (such as `dropdown`, `tabs` and `settings`) are reusable, while some are specific to certain pages (eg `debug`, `home`, `onboarding`). These can be broken down into more granular modules as the test suite grows.
|
||||
|
||||
### Extend tests
|
||||
|
||||
Unlike unit tests, the application startup time for a smoke test can sometimes be longer than the test itself. As such, in cases where it is appropriate, **extend** a smoke test with additional steps instead of creating a **new** test.
|
||||
|
||||
## Working with fixtures
|
||||
|
||||
### How to update the inso-nedb fixture
|
||||
@ -235,26 +98,8 @@ Set the `--src packages/insomnia-smoke-test/fixtures/inso-nedb` flag
|
||||
```bash
|
||||
# if installed globally
|
||||
inso --src ...
|
||||
|
||||
# using the package bin
|
||||
./packages/insomnia-inso/bin/inso --src ...
|
||||
|
||||
# using a binary
|
||||
./packages/insomnia-inso/binaries/insomnia-inso --src ...
|
||||
```
|
||||
|
||||
## Contributing a smoke test?
|
||||
|
||||
Smoke tests can potentially be flaky, and one attempt to avoid flaky tests in the default branch is to run the final implementation of a test at least 20 times locally to prove its stability. If a test is unable to achieve this, it is very unlikely to be accepted into the test suite.
|
||||
|
||||
You can repeat a test quickly by wrapping it with the following block:
|
||||
|
||||
```ts
|
||||
describe.only.each(new Array(20).fill(1))('iteration %#', _ => {
|
||||
it('your test name', () => {
|
||||
//...
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
When raising a PR, paste a screenshot of the test results showing at least 20 successful iterations.
|
||||
|
@ -1,213 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import { basicAuthCreds } from '../fixtures/constants';
|
||||
import { launchApp, stop } from '../modules/application';
|
||||
import * as client from '../modules/client';
|
||||
import * as debug from '../modules/debug';
|
||||
import * as dropdown from '../modules/dropdown';
|
||||
import * as home from '../modules/home';
|
||||
import * as modal from '../modules/modal';
|
||||
import * as onboarding from '../modules/onboarding';
|
||||
import { waitUntilTextDisappears } from '../modules/text';
|
||||
|
||||
describe('Application launch', function() {
|
||||
jest.setTimeout(50000);
|
||||
let app: Application;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await launchApp();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stop(app);
|
||||
});
|
||||
|
||||
it('shows an initial window', async () => {
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
await home.documentListingShown(app);
|
||||
});
|
||||
|
||||
it('sends JSON request', async () => {
|
||||
const url = 'http://127.0.0.1:4010/pets/{% now \'millis\', \'\' %}';
|
||||
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.createNewCollection(app);
|
||||
await debug.pageDisplayed(app);
|
||||
|
||||
await debug.createNewRequest(app, 'json');
|
||||
await debug.typeInUrlBar(app, url);
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
});
|
||||
|
||||
it('sends dummy.csv request and shows rich response', async () => {
|
||||
const url = 'http://127.0.0.1:4010/file/dummy.csv';
|
||||
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.createNewCollection(app);
|
||||
await debug.pageDisplayed(app);
|
||||
|
||||
await debug.createNewRequest(app, 'csv');
|
||||
await debug.typeInUrlBar(app, url);
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
const csvViewer = await debug.getCsvViewer(app);
|
||||
await expect(csvViewer.getText()).resolves.toBe('a b c\n1 2 3');
|
||||
});
|
||||
|
||||
it('sends dummy.xml request and shows raw response', async () => {
|
||||
const url = 'http://127.0.0.1:4010/file/dummy.xml';
|
||||
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.createNewCollection(app);
|
||||
await debug.pageDisplayed(app);
|
||||
|
||||
await debug.createNewRequest(app, 'xml');
|
||||
await debug.typeInUrlBar(app, url);
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
|
||||
const responseViewer = await debug.getResponseViewer(app);
|
||||
const partialExpectedResponse = '<LoginResult>xxx-777-xxx-123</LoginResult>';
|
||||
|
||||
await debug.expectContainsText(responseViewer, partialExpectedResponse);
|
||||
|
||||
await debug.typeInResponseFilter(app, "//*[local-name(.)='LoginResult']/text()");
|
||||
await waitUntilTextDisappears(app, responseViewer, partialExpectedResponse);
|
||||
|
||||
await debug.expectContainsText(
|
||||
responseViewer,
|
||||
'<result>xxx-777-xxx-123</result>',
|
||||
);
|
||||
});
|
||||
|
||||
it('sends dummy.pdf request and shows rich response', async () => {
|
||||
const url = 'http://127.0.0.1:4010/file/dummy.pdf';
|
||||
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.createNewCollection(app);
|
||||
await debug.pageDisplayed(app);
|
||||
|
||||
await debug.createNewRequest(app, 'pdf');
|
||||
await debug.typeInUrlBar(app, url);
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
const pdfCanvas = await debug.getPdfCanvas(app);
|
||||
// Investigate how we can extract text from the canvas, or compare images
|
||||
await expect(pdfCanvas.isExisting()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
// NOTE: skipped because plugins are pulled from npm in CI rather than read from this repo
|
||||
// TODO: unskip this test after ticket INS-502 corrects the above
|
||||
it.skip('shows deploy to dev portal for design documents', async () => {
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
const docName = await home.createNewDocument(app);
|
||||
await debug.goToDashboard(app);
|
||||
|
||||
// Open card dropdown for the document
|
||||
const card = await home.findCardWithTitle(app, docName);
|
||||
await home.openWorkspaceCardDropdown(card);
|
||||
|
||||
// Click the "Deploy to Dev Portal" button, installed from that plugin
|
||||
await dropdown.clickOpenDropdownItemByText(app, 'Deploy to Dev Portal');
|
||||
|
||||
// Ensure a modal opens, then close it - the rest is plugin behavior
|
||||
await modal.waitUntilOpened(app, { title: 'Deploy to Dev Portal' });
|
||||
await modal.close(app);
|
||||
});
|
||||
|
||||
// This test will ensure that for an endpoint which expects basic auth:
|
||||
// 1. sending no basic auth will fail
|
||||
// 2. sending basic auth will succeed
|
||||
// 3. sending basic auth with special characters encoded with IS0-8859-1 will succeed
|
||||
// 4. sending while basic auth is disabled within insomnia will fail
|
||||
// TODO(TSCONVERSION) - this test fails fairly readily after TS conversion, needs investigation
|
||||
it.skip('sends request with basic authentication', async () => {
|
||||
const url = 'http://127.0.0.1:4010/auth/basic';
|
||||
const { latin1, utf8 } = basicAuthCreds;
|
||||
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.createNewCollection(app);
|
||||
await debug.pageDisplayed(app);
|
||||
|
||||
await debug.createNewRequest(app, 'basic-auth');
|
||||
await debug.typeInUrlBar(app, url);
|
||||
|
||||
// Send request with no auth present
|
||||
await debug.clickSendRequest(app);
|
||||
await debug.expect401(app);
|
||||
|
||||
// Click auth tab
|
||||
await debug.clickRequestAuthTab(app);
|
||||
await debug.expectNoAuthSelected(app);
|
||||
|
||||
// Select basic auth
|
||||
await debug.clickRequestAuthDropdown(app);
|
||||
await debug.clickBasicAuth(app);
|
||||
|
||||
// Enter username and password with regular characters
|
||||
await debug.typeBasicAuthUsernameAndPassword(app, utf8.raw.user, utf8.raw.pass);
|
||||
|
||||
// Send request with auth present
|
||||
await debug.clickSendRequest(app);
|
||||
await debug.expect200(app);
|
||||
|
||||
const responseViewer = await debug.getResponseViewer(app);
|
||||
await debug.expectText(responseViewer, '1\nbasic auth received');
|
||||
|
||||
// Check auth header in timeline
|
||||
await debug.clickTimelineTab(app);
|
||||
|
||||
await debug.expectContainsText(
|
||||
await debug.getTimelineViewer(app),
|
||||
`> Authorization: Basic ${utf8.combined}`,
|
||||
);
|
||||
|
||||
// Clear inputs and type username/password with special characters
|
||||
await debug.typeBasicAuthUsernameAndPassword(app, latin1.raw.user, latin1.raw.pass, true);
|
||||
|
||||
// Toggle basic auth and encoding enabled
|
||||
await debug.toggleBasicAuthEncoding(app);
|
||||
|
||||
// Send request
|
||||
await debug.clickSendRequest(app);
|
||||
await debug.expect200(app);
|
||||
|
||||
await debug.expectContainsText(
|
||||
await debug.getTimelineViewer(app),
|
||||
`> Authorization: Basic ${latin1.combined}`,
|
||||
);
|
||||
|
||||
// Toggle basic auth to disabled
|
||||
await debug.toggleBasicAuthEnabled(app);
|
||||
|
||||
// Send request
|
||||
await debug.clickSendRequest(app);
|
||||
await debug.expect401(app);
|
||||
|
||||
await debug.expectNotContainsText(await debug.getTimelineViewer(app), '> Authorization: Basic');
|
||||
});
|
||||
});
|
@ -1,273 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import { launchApp, stop, writeTextToClipboard } from '../modules/application';
|
||||
import * as client from '../modules/client';
|
||||
import * as debug from '../modules/debug';
|
||||
import * as design from '../modules/design';
|
||||
import { loadFixture } from '../modules/fixtures';
|
||||
import * as home from '../modules/home';
|
||||
import * as modal from '../modules/modal';
|
||||
import * as onboarding from '../modules/onboarding';
|
||||
import * as settings from '../modules/settings';
|
||||
|
||||
describe.only('Import', function() {
|
||||
jest.setTimeout(50000);
|
||||
|
||||
let app: Application;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await launchApp();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stop(app);
|
||||
});
|
||||
|
||||
describe('from the Dashboard', () => {
|
||||
it('should create a new workspace if there are no available workspaces', async () => {
|
||||
// Launch the app
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.expectTotalDocuments(app, 0);
|
||||
|
||||
// Import the fixture from the clipboard
|
||||
const swagger2Text = await loadFixture('swagger2.yaml');
|
||||
|
||||
writeTextToClipboard(app, swagger2Text);
|
||||
|
||||
await home.importFromClipboard(app);
|
||||
|
||||
// Import as a design document
|
||||
await modal.waitUntilOpened(app, { title: 'Import As' });
|
||||
await modal.clickModalFooterByText(app, 'Design Document');
|
||||
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Open the new document and send a request
|
||||
await home.openDocumentWithTitle(app, 'E2E testing specification - swagger 2 1.0.0');
|
||||
|
||||
await design.goToActivity(app, 'debug');
|
||||
|
||||
await debug.clickFolderByName(app, 'custom-tag');
|
||||
|
||||
await debug.clickRequestByName(app, 'get pet by id');
|
||||
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
});
|
||||
|
||||
it('should prompt the user to import to a new workspace', async () => {
|
||||
// Launch the app
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
|
||||
// Create a document
|
||||
await home.createNewCollection(app);
|
||||
await debug.goToDashboard(app);
|
||||
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Import the fixture from the clipboard
|
||||
const swagger2Text = await loadFixture('swagger2.yaml');
|
||||
|
||||
writeTextToClipboard(app, swagger2Text);
|
||||
|
||||
await home.importFromClipboard(app);
|
||||
|
||||
// Chose to import to a new workspace as a design document
|
||||
await modal.waitUntilOpened(app, { title: 'Import' });
|
||||
await modal.clickModalFooterByText(app, 'New');
|
||||
|
||||
await modal.waitUntilOpened(app, { title: 'Import As' });
|
||||
await modal.clickModalFooterByText(app, 'Design Document');
|
||||
|
||||
await home.expectTotalDocuments(app, 2);
|
||||
|
||||
// Open the new document and send a request
|
||||
await home.openDocumentWithTitle(app, 'E2E testing specification - swagger 2 1.0.0');
|
||||
|
||||
await design.goToActivity(app, 'debug');
|
||||
|
||||
await debug.clickFolderByName(app, 'custom-tag');
|
||||
|
||||
await debug.clickRequestByName(app, 'get pet by id');
|
||||
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
});
|
||||
|
||||
it('should prompt the user to import to an existing workspace', async () => {
|
||||
// Launch the app
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
|
||||
// Create a document
|
||||
const newCollectionName = await home.createNewCollection(app, 'New');
|
||||
await debug.goToDashboard(app);
|
||||
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Import the fixture from the clipboard
|
||||
const swagger2Text = await loadFixture('swagger2.yaml');
|
||||
|
||||
writeTextToClipboard(app, swagger2Text);
|
||||
|
||||
await home.importFromClipboard(app);
|
||||
|
||||
// Chose to import to the existing document
|
||||
await modal.waitUntilOpened(app, { title: 'Import' });
|
||||
await modal.clickModalFooterByText(app, 'Existing');
|
||||
|
||||
await modal.selectModalOption(app, newCollectionName);
|
||||
await modal.clickModalFooterByText(app, 'Done');
|
||||
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Since the workspace we import has a name it overrides the existing one
|
||||
// Open the new document and send a request
|
||||
await home.openDocumentWithTitle(app, 'E2E testing specification - swagger 2 1.0.0');
|
||||
|
||||
await debug.clickFolderByName(app, 'custom-tag');
|
||||
|
||||
await debug.clickRequestByName(app, 'get pet by id');
|
||||
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
});
|
||||
|
||||
it('should update the existing workspace (e.g. Insomnia Exports)', async () => {
|
||||
// Launch the app
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
|
||||
await home.expectTotalDocuments(app, 0);
|
||||
|
||||
// Import the fixture from the clipboard
|
||||
const insomnia4Text = await loadFixture('insomnia4.yaml');
|
||||
|
||||
writeTextToClipboard(app, insomnia4Text);
|
||||
|
||||
await home.importFromClipboard(app);
|
||||
|
||||
// Chose to import as a collection
|
||||
await modal.waitUntilOpened(app, { title: 'Import As' });
|
||||
await modal.clickModalFooterByText(app, 'Request Collection');
|
||||
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Import the spec a second time
|
||||
await home.importFromClipboard(app);
|
||||
|
||||
// It should update the existing document
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Open the new document and send a request
|
||||
await home.openDocumentWithTitle(app, 'Insomnia');
|
||||
|
||||
await debug.clickFolderByName(app, 'Actions');
|
||||
|
||||
await debug.clickRequestByName(app, 'Get Sleep');
|
||||
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
});
|
||||
});
|
||||
|
||||
describe('from Preferences', () => {
|
||||
it('should directly import to the active workspace', async () => {
|
||||
// Launch the app
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.expectTotalDocuments(app, 0);
|
||||
|
||||
// Create a document and open it
|
||||
await home.createNewCollection(app);
|
||||
|
||||
// Import the fixture from the preferences panel
|
||||
const swagger2Text = await loadFixture('swagger2.yaml');
|
||||
|
||||
writeTextToClipboard(app, swagger2Text);
|
||||
|
||||
await settings.openFromSettingsButton(app);
|
||||
await settings.goToDataTab(app);
|
||||
await settings.importFromClipboard(app);
|
||||
|
||||
// Send a request
|
||||
await debug.clickFolderByName(app, 'custom-tag');
|
||||
await debug.clickRequestByName(app, 'get pet by id');
|
||||
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
|
||||
// Navigate to the dashboard and check no other workspace was created
|
||||
await debug.goToDashboard(app);
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
});
|
||||
|
||||
it('should import an existing workspace to the project instead of the current workspace (e.g. Insomnia Exports)', async () => {
|
||||
// Launch the app
|
||||
await client.correctlyLaunched(app);
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.expectTotalDocuments(app, 0);
|
||||
// Import the fixture from the clipboard
|
||||
const insomnia4Text = await loadFixture('insomnia4.yaml');
|
||||
|
||||
writeTextToClipboard(app, insomnia4Text);
|
||||
|
||||
await home.importFromClipboard(app);
|
||||
|
||||
await modal.waitUntilOpened(app, { title: 'Import As' });
|
||||
await modal.clickModalFooterByText(app, 'Design Document');
|
||||
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
|
||||
// Create a document and open it
|
||||
await home.createNewCollection(app);
|
||||
|
||||
// Import the updated fixture from the preferences panel
|
||||
const insomnia4UpdatedText = await loadFixture('insomnia4-update.yaml');
|
||||
|
||||
writeTextToClipboard(app, insomnia4UpdatedText);
|
||||
await settings.openFromSettingsButton(app);
|
||||
await settings.goToDataTab(app);
|
||||
await settings.importFromClipboard(app);
|
||||
|
||||
// NOTE: The document was imported directly to the
|
||||
// previously imported workspace and the user didn't see a dialog
|
||||
|
||||
// Open the previous document and check that it's updated
|
||||
await debug.goToDashboard(app);
|
||||
|
||||
await home.expectTotalDocuments(app, 2);
|
||||
|
||||
await home.openDocumentWithTitle(app, 'Insomnia');
|
||||
|
||||
await design.goToActivity(app, 'debug');
|
||||
|
||||
await debug.clickFolderByName(app, 'Actions');
|
||||
|
||||
await debug.clickRequestByName(app, 'Get Sleep 2');
|
||||
|
||||
await debug.clickSendRequest(app);
|
||||
|
||||
await debug.expect200(app);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,66 +0,0 @@
|
||||
import path from 'path';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import { launchApp, stop } from '../modules/application';
|
||||
import * as client from '../modules/client';
|
||||
import * as home from '../modules/home';
|
||||
import * as migration from '../modules/migration';
|
||||
import * as onboarding from '../modules/onboarding';
|
||||
|
||||
describe('Migration', function() {
|
||||
jest.setTimeout(50000);
|
||||
let app: Application;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await launchApp(path.join(__dirname, '..', 'fixtures', 'basic-designer'));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stop(app);
|
||||
});
|
||||
|
||||
it('can skip migration and proceed onboarding', async () => {
|
||||
await client.correctlyLaunched(app);
|
||||
|
||||
await migration.migrationMessageShown(app);
|
||||
await migration.clickSkip(app);
|
||||
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.expectTotalDocuments(app, 0);
|
||||
|
||||
await app.restart();
|
||||
await client.focusAfterRestart(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
});
|
||||
|
||||
it('can migrate and proceed onboarding', async () => {
|
||||
await client.correctlyLaunched(app);
|
||||
|
||||
await migration.migrationMessageShown(app);
|
||||
await migration.ensureStartNotClickable(app);
|
||||
|
||||
await migration.toggleOption(app, 'Copy Workspaces');
|
||||
await migration.toggleOption(app, 'Copy Plugins');
|
||||
await migration.toggleOption(app, 'Copy Designer Application Settings');
|
||||
await migration.clickStart(app);
|
||||
|
||||
await migration.successMessageShown(app);
|
||||
await migration.clickRestart(app);
|
||||
|
||||
await client.focusAfterRestart(app);
|
||||
|
||||
await onboarding.skipOnboardingFlow(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
await home.expectTotalDocuments(app, 1);
|
||||
await home.expectDocumentWithTitle(app, 'BASIC-DESIGNER-FIXTURE'); // imported from fixture
|
||||
|
||||
await app.restart();
|
||||
await client.focusAfterRestart(app);
|
||||
|
||||
await home.documentListingShown(app);
|
||||
});
|
||||
});
|
@ -1,47 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import { isPackage, launchApp, stop } from '../modules/application';
|
||||
import * as client from '../modules/client';
|
||||
import * as dropdown from '../modules/dropdown';
|
||||
import * as home from '../modules/home';
|
||||
import * as modal from '../modules/modal';
|
||||
import * as settings from '../modules/settings';
|
||||
|
||||
const itIf = condition => (condition ? it : it.skip);
|
||||
// @ts-expect-error TSCONVERSION
|
||||
it.if = itIf;
|
||||
|
||||
xdescribe('Application launch', function() {
|
||||
jest.setTimeout(50000);
|
||||
let app: Application;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await launchApp();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await stop(app);
|
||||
});
|
||||
|
||||
// @ts-expect-error TSCONVERSION
|
||||
xit.if(isPackage())('can install and consume a plugin', async () => {
|
||||
await client.correctlyLaunched(app);
|
||||
await home.documentListingShown(app);
|
||||
|
||||
// Install plugin
|
||||
await settings.openWithKeyboardShortcut(app);
|
||||
await settings.goToPluginsTab(app);
|
||||
await settings.installPlugin(app, 'insomnia-plugin-kong-portal');
|
||||
await settings.closeModal(app);
|
||||
|
||||
// Open card dropdown for any card
|
||||
const dd = await home.openWorkspaceCardDropdown(app);
|
||||
|
||||
// Click the "Deploy to Dev Portal" button, installed from that plugin
|
||||
await dropdown.clickDropdownItemByText(dd, 'Deploy to Dev Portal');
|
||||
|
||||
// Ensure a modal opens, then close it - the rest is plugin behavior
|
||||
await modal.waitUntilOpened(app, { title: 'Deploy to Dev Portal' });
|
||||
await modal.close(app);
|
||||
});
|
||||
});
|
190
packages/insomnia-smoke-test/fixtures/smoke-test-collection.yaml
Normal file
190
packages/insomnia-smoke-test/fixtures/smoke-test-collection.yaml
Normal file
@ -0,0 +1,190 @@
|
||||
_type: export
|
||||
__export_format: 4
|
||||
__export_date: 2021-12-03T07:42:43.250Z
|
||||
__export_source: insomnia.desktop.app:v2021.6.0
|
||||
resources:
|
||||
- _id: req_178f98ccc6b240f5a1f67c492b6bf9a6
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636142586648
|
||||
created: 1636141100570
|
||||
url: http://127.0.0.1:4010/auth/basic
|
||||
name: sends request with basic authentication
|
||||
description: ""
|
||||
method: GET
|
||||
body: {}
|
||||
parameters: []
|
||||
headers:
|
||||
- id: pair_cbcd8bc34d494a3c83b29989d06cf005
|
||||
name: Authorization
|
||||
value: Basic dXNlcjpwYXNz
|
||||
description: ""
|
||||
disabled: true
|
||||
authentication:
|
||||
type: basic
|
||||
useISO88591: false
|
||||
disabled: false
|
||||
username: user
|
||||
password: pass
|
||||
metaSortKey: -1636141100570
|
||||
isPrivate: false
|
||||
settingStoreCookies: true
|
||||
settingSendCookies: true
|
||||
settingDisableRenderRequestBody: false
|
||||
settingEncodeUrl: true
|
||||
settingRebuildPath: true
|
||||
settingFollowRedirects: global
|
||||
_type: request
|
||||
- _id: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
parentId: null
|
||||
modified: 1636140994423
|
||||
created: 1636140994423
|
||||
name: Smoke tests
|
||||
description: ""
|
||||
scope: collection
|
||||
_type: workspace
|
||||
- _id: req_e6fab3568ed54f9d83c92e2c8006e140
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636141084436
|
||||
created: 1636141078601
|
||||
url: http://127.0.0.1:4010/file/dummy.pdf
|
||||
name: sends dummy.pdf request and shows rich response
|
||||
description: ""
|
||||
method: GET
|
||||
body: {}
|
||||
parameters: []
|
||||
headers: []
|
||||
authentication: {}
|
||||
metaSortKey: -1636141078601
|
||||
isPrivate: false
|
||||
settingStoreCookies: true
|
||||
settingSendCookies: true
|
||||
settingDisableRenderRequestBody: false
|
||||
settingEncodeUrl: true
|
||||
settingRebuildPath: true
|
||||
settingFollowRedirects: global
|
||||
_type: request
|
||||
- _id: req_06a660bffe724e3fac14d7d09b4a5c9b
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636141067089
|
||||
created: 1636141061433
|
||||
url: http://127.0.0.1:4010/file/dummy.xml
|
||||
name: sends dummy.xml request and shows raw response
|
||||
description: ""
|
||||
method: GET
|
||||
body: {}
|
||||
parameters: []
|
||||
headers: []
|
||||
authentication: {}
|
||||
metaSortKey: -1636141061433
|
||||
isPrivate: false
|
||||
settingStoreCookies: true
|
||||
settingSendCookies: true
|
||||
settingDisableRenderRequestBody: false
|
||||
settingEncodeUrl: true
|
||||
settingRebuildPath: true
|
||||
settingFollowRedirects: global
|
||||
_type: request
|
||||
- _id: req_ee610ff89152476ea25950f39ca59819
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636141047337
|
||||
created: 1636141038448
|
||||
url: http://127.0.0.1:4010/file/dummy.csv
|
||||
name: sends dummy.csv request and shows rich response
|
||||
description: ""
|
||||
method: GET
|
||||
body: {}
|
||||
parameters: []
|
||||
headers: []
|
||||
authentication: {}
|
||||
metaSortKey: -1636141038448
|
||||
isPrivate: false
|
||||
settingStoreCookies: true
|
||||
settingSendCookies: true
|
||||
settingDisableRenderRequestBody: false
|
||||
settingEncodeUrl: true
|
||||
settingRebuildPath: true
|
||||
settingFollowRedirects: global
|
||||
_type: request
|
||||
- _id: req_89dade2ee9ee42fbb22d588783a9df3b
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636707449231
|
||||
created: 1636141014552
|
||||
url: http://127.0.0.1:4010/pets/1
|
||||
name: send JSON request
|
||||
description: ""
|
||||
method: GET
|
||||
body: {}
|
||||
parameters: []
|
||||
headers:
|
||||
- id: pair_4c3fe3092f1245eab6d960c633d8be9c
|
||||
name: test
|
||||
value: test
|
||||
description: ""
|
||||
authentication: {}
|
||||
metaSortKey: -1636141014552
|
||||
isPrivate: false
|
||||
settingStoreCookies: true
|
||||
settingSendCookies: true
|
||||
settingDisableRenderRequestBody: false
|
||||
settingEncodeUrl: true
|
||||
settingRebuildPath: true
|
||||
settingFollowRedirects: global
|
||||
_type: request
|
||||
- _id: env_afc214be117853a024cce22f194c500e68dac13f
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636140994432
|
||||
created: 1636140994432
|
||||
name: Base Environment
|
||||
data: {}
|
||||
dataPropertyOrder: null
|
||||
color: null
|
||||
isPrivate: false
|
||||
metaSortKey: 1636140994432
|
||||
_type: environment
|
||||
- _id: jar_afc214be117853a024cce22f194c500e68dac13f
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1637279629638
|
||||
created: 1636140994434
|
||||
name: Default Jar
|
||||
cookies:
|
||||
- key: foo
|
||||
value: bar
|
||||
expires: null
|
||||
domain: domain.com
|
||||
path: /
|
||||
creation: 2021-11-18T23:53:05.310Z
|
||||
id: "429589439757017"
|
||||
- key: has_recent_activity
|
||||
value: "1"
|
||||
expires: 2021-11-19T00:53:49.000Z
|
||||
domain: github.com
|
||||
path: /
|
||||
secure: true
|
||||
httpOnly: true
|
||||
extensions:
|
||||
- SameSite=Lax
|
||||
hostOnly: true
|
||||
creation: 2021-11-18T23:53:05.311Z
|
||||
lastAccessed: 2021-11-18T23:53:49.637Z
|
||||
id: "09630096071226024"
|
||||
- key: _gh_sess
|
||||
value: hno6H6Q2M8RK8eDeSpwNIhlNCDPsh%2BTLYCqW37gBtKvE2iHWyuCzusnHmN2M2zqL5fm4vNek98BlbXrBb2xPI%2Fsx9Q%2FPaIPJBeQ%2BJLlAetGuXnjucDjb7jwVFD07jFxPJ8n8%2F3i6yk2II%2BPyPQOfs5y45g4i5JLxhpu5BE6JGj02Jf8PLgXnvNpILm3QiLyT4oIuxqZFEbko%2BdmdIoG8qSs9hUiLBsYp1KgM8v7Jq7EWbb%2BkmVQDFnDkmvFIV6K5Usu6owlZQwM7XTo5KYef%2FcF63ic1o1%2F5JdDHHPoTsW4h68MFGEcPBqxHeGAa7Cw7LV%2FrnsvrNtsAtfMih%2FYI%2FeKn5jk3ChUN7cV43retCmIApNMd%2FFGAeuf4pmmvqbFLHtPk6zd8dXRSdwPGHp608GpTqWhRrh%2FNMnydgZj86h9668yLRNWeii%2B1JToNnpl8gU6iYNUEJuBPlZJ%2BTT5H4ZtNskY5Ccw8ysPLKH1qUtwXc%2ByJPIvYI1fGPNpX8BcLHhM0m5ru0jdEBvOsXd%2FbPdt%2B0%2BKbQEqUyig3hygN3C7bfAkE3%2B1IOhFYdWcMR83msFf1jvuE%2FoWw%2FGOPHYhiO48ePz7V9ZwqOuBG7ikUyfS07XjeTgqPrO5P5ON7YowCK%2F7x7rt%2Beu84Pi8Uc%2FA4aGhQU%2B8Ixn%2BrvnqYMDLlsYwP%2BTcoHLPEKe4IFDWjoTJehae2%2FQ4WAE88L%2FTgXAeqnO4%2BHaiFtoZYfLDMS4Xm0cB7MRC7C%2BaCvr90j56emc13r1%2Fgeg%3D%3D--ZIqRrsGB9DwAmnTG--xwQhqF9WAvbb5FNbLruEzQ%3D%3D
|
||||
domain: github.com
|
||||
path: /
|
||||
secure: true
|
||||
httpOnly: true
|
||||
extensions:
|
||||
- SameSite=Lax
|
||||
hostOnly: true
|
||||
creation: 2021-11-18T23:53:05.312Z
|
||||
lastAccessed: 2021-11-18T23:53:49.637Z
|
||||
id: "5403425689099031"
|
||||
_type: cookie_jar
|
||||
- _id: spc_255904e6a8774d24b29c9c3718feb07f
|
||||
parentId: wrk_5b5ab67830944ffcbec47528366ef403
|
||||
modified: 1636140994428
|
||||
created: 1636140994428
|
||||
fileName: Smoke tests
|
||||
contents: ""
|
||||
contentType: yaml
|
||||
_type: api_spec
|
@ -1,117 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import mkdirp from 'mkdirp';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
// @ts-expect-error TSCONVERSION
|
||||
import electronPath from '../../insomnia-app/node_modules/electron';
|
||||
|
||||
const getAppPlatform = () => process.platform;
|
||||
const isMac = () => getAppPlatform() === 'darwin';
|
||||
const isLinux = () => getAppPlatform() === 'linux';
|
||||
const isWindows = () => getAppPlatform() === 'win32';
|
||||
|
||||
export const isBuild = () => process.env.BUNDLE === 'build';
|
||||
export const isPackage = () => process.env.BUNDLE === 'package';
|
||||
|
||||
const spectronConfig = (
|
||||
designerDataPath = path.join(__dirname, '..', 'fixtures', 'doesnt-exist'),
|
||||
) => {
|
||||
let packagePathSuffix = '';
|
||||
if (isWindows()) {
|
||||
packagePathSuffix = path.join('win-unpacked', 'Insomnia.exe');
|
||||
} else if (isMac()) {
|
||||
packagePathSuffix = path.join('mac', 'Insomnia.app', 'Contents', 'MacOS', 'Insomnia');
|
||||
} else if (isLinux()) {
|
||||
packagePathSuffix = ''; // TODO: find out what this is
|
||||
}
|
||||
|
||||
const buildPath = path.join(__dirname, '../../insomnia-app/build');
|
||||
const packagePath = path.join(__dirname, '../../insomnia-app/dist', packagePathSuffix);
|
||||
const dataPath = path.join(os.tmpdir(), 'insomnia-smoke-test', `${Date.now()}`);
|
||||
const env = { INSOMNIA_DATA_PATH: dataPath };
|
||||
|
||||
if (designerDataPath) {
|
||||
// @ts-expect-error TSCONVERSION
|
||||
env.DESIGNER_DATA_PATH = designerDataPath;
|
||||
}
|
||||
|
||||
return { buildPath, packagePath, env };
|
||||
};
|
||||
|
||||
export const launchApp = async (designerDataPath?: string) => {
|
||||
const config = spectronConfig(designerDataPath);
|
||||
return await launch(config);
|
||||
};
|
||||
|
||||
const getLaunchPath = config =>
|
||||
isPackage()
|
||||
? { path: config.packagePath }
|
||||
: {
|
||||
path: electronPath,
|
||||
args: [config.buildPath],
|
||||
};
|
||||
|
||||
const launch = async config => {
|
||||
if (!config) {
|
||||
throw new Error('Spectron config could not be loaded.');
|
||||
}
|
||||
|
||||
const app = new Application({
|
||||
...getLaunchPath(config),
|
||||
|
||||
// Don't remove chromeDriverArgs
|
||||
// https://github.com/electron-userland/spectron/issues/353#issuecomment-522846725
|
||||
chromeDriverArgs: ['remote-debugging-port=9222'],
|
||||
|
||||
env: config.env,
|
||||
|
||||
startTimeout: 10000,
|
||||
waitTimeout: 10000,
|
||||
});
|
||||
|
||||
await app.start().then(async () => {
|
||||
// Windows spawns two terminal windows when running spectron, and the only workaround
|
||||
// is to focus the window on start.
|
||||
// https://github.com/electron-userland/spectron/issues/60
|
||||
await app.browserWindow.focus();
|
||||
await app.browserWindow.setAlwaysOnTop(true);
|
||||
|
||||
// Set the implicit wait timeout to 0 (webdriver default)
|
||||
// https://webdriver.io/docs/timeouts.html#session-implicit-wait-timeout
|
||||
// Spectron overrides it to an unreasonable value, as per the issue
|
||||
// https://github.com/electron-userland/spectron/issues/763
|
||||
await app.client.setTimeout({ implicit: 0 });
|
||||
|
||||
// Set bounds to default size
|
||||
await app.browserWindow.setSize(1280, 700);
|
||||
});
|
||||
return app;
|
||||
};
|
||||
|
||||
export const stop = async app => {
|
||||
await takeScreenshotOnFailure(app);
|
||||
|
||||
if (app?.isRunning()) {
|
||||
await app.stop();
|
||||
}
|
||||
};
|
||||
|
||||
const takeScreenshotOnFailure = async app => {
|
||||
// @ts-expect-error TSCONVERSION
|
||||
if (jasmine.currentTest.failedExpectations.length) {
|
||||
// @ts-expect-error TSCONVERSION
|
||||
await takeScreenshot(app, jasmine.currentTest.fullName.replace(/ /g, '_'));
|
||||
}
|
||||
};
|
||||
|
||||
export const takeScreenshot = async (app, name) => {
|
||||
mkdirp.sync('screenshots');
|
||||
const buffer = await app.browserWindow.capturePage();
|
||||
await fs.promises.writeFile(path.join('screenshots', `${name}.png`), buffer);
|
||||
};
|
||||
|
||||
export const writeTextToClipboard = (app: Application, text: string) => {
|
||||
app.electron.clipboard.writeText(text);
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
export const correctlyLaunched = async app => {
|
||||
await expect(app.browserWindow.isDevToolsOpened()).resolves.toBe(false);
|
||||
await expect(app.client.getWindowCount()).resolves.toBe(1);
|
||||
await expect(app.browserWindow.isMinimized()).resolves.toBe(false);
|
||||
await expect(app.browserWindow.isFocused()).resolves.toBe(true);
|
||||
};
|
||||
|
||||
export const focusAfterRestart = async app => {
|
||||
await app.client.pause(4000);
|
||||
|
||||
const count = await app.client.getWindowCount();
|
||||
if (count === 0) {
|
||||
console.log('No windows found');
|
||||
}
|
||||
|
||||
await app.client.windowByIndex(0);
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
export const COMPONENT_NAMES = {
|
||||
sidebarRequestRow: 'UnconnectedSidebarRequestRow',
|
||||
sidebarRequestGroupRow: 'UnconnectedSidebarRequestGroupRow',
|
||||
requestUrlBar: 'RequestUrlBar',
|
||||
};
|
@ -1,258 +0,0 @@
|
||||
import faker from 'faker';
|
||||
import { Application } from 'spectron';
|
||||
import spectronKeys from 'spectron-keys';
|
||||
|
||||
import { COMPONENT_NAMES } from './component-names';
|
||||
|
||||
export const workspaceDropdownExists = async (app: Application, workspaceName = 'Insomnia') => {
|
||||
await app.client.waitUntilTextExists('.workspace-dropdown', workspaceName);
|
||||
};
|
||||
|
||||
export const pageDisplayed = async (app: Application) => {
|
||||
await app.client.react$('WrapperDebug').then(e => e.waitForDisplayed());
|
||||
};
|
||||
|
||||
export const clickWorkspaceDropdown = async (app: Application) => {
|
||||
const dropdown = await app.client.react$('WorkspaceDropdown');
|
||||
await dropdown.click();
|
||||
return dropdown;
|
||||
};
|
||||
|
||||
export const goToDashboard = async (app: Application) => {
|
||||
await app.client.$('.header_left [data-testid="project"]').then(e => e.click());
|
||||
};
|
||||
|
||||
export const createNewRequest = async (app: Application, name: string) => {
|
||||
await app.client.$('.sidebar .dropdown .fa-plus-circle').then(e => e.click());
|
||||
|
||||
await app.client
|
||||
.$('[aria-hidden=false]')
|
||||
.then(e => e.$('button*=New Request'))
|
||||
.then(e => e.click());
|
||||
|
||||
// Wait for modal to open
|
||||
await app.client.waitUntilTextExists('.modal__header', 'New Request');
|
||||
|
||||
// Set name and create request
|
||||
const input = await app.client.$('.modal input');
|
||||
|
||||
const requestName = `${name}-${faker.lorem.slug()}`;
|
||||
await input.waitUntil(() => input.isFocused());
|
||||
await input.keys(requestName);
|
||||
|
||||
await app.client
|
||||
.$('.modal .modal__footer')
|
||||
.then(e => e.$('button=Create'))
|
||||
.then(e => e.click());
|
||||
|
||||
await waitUntilRequestIsActive(app, requestName);
|
||||
};
|
||||
|
||||
const waitUntilRequestIsActive = async (app: Application, name: string) => {
|
||||
const request = await app.client.react$(COMPONENT_NAMES.sidebarRequestRow, {
|
||||
props: { isActive: true, request: { name } },
|
||||
});
|
||||
|
||||
await request.waitForDisplayed();
|
||||
};
|
||||
|
||||
export const clickFolderByName = async (app: Application, name: string) => {
|
||||
const folder = await app.client.react$(COMPONENT_NAMES.sidebarRequestGroupRow, {
|
||||
props: { requestGroup: { name } },
|
||||
});
|
||||
|
||||
await folder.waitForClickable();
|
||||
await folder.click();
|
||||
};
|
||||
|
||||
export const clickRequestByName = async (app: Application, name: string) => {
|
||||
const folder = await app.client.react$(COMPONENT_NAMES.sidebarRequestRow, {
|
||||
props: { request: { name } },
|
||||
});
|
||||
|
||||
await folder.waitForClickable();
|
||||
await folder.click();
|
||||
};
|
||||
|
||||
export const typeInUrlBar = async (app: Application, url) => {
|
||||
const urlEditor = await app.client.react$(COMPONENT_NAMES.requestUrlBar);
|
||||
await urlEditor.waitForExist();
|
||||
await urlEditor.click();
|
||||
await urlEditor.keys(url);
|
||||
};
|
||||
|
||||
export const clickSendRequest = async (app: Application) => {
|
||||
await app.client
|
||||
.react$(COMPONENT_NAMES.requestUrlBar)
|
||||
.then(e => e.$('.urlbar__send-btn'))
|
||||
.then(e => e.click());
|
||||
|
||||
// Wait for spinner to show
|
||||
const spinner = await app.client.react$('ResponseTimer');
|
||||
await spinner.waitForDisplayed();
|
||||
|
||||
// Wait for spinner to hide
|
||||
await spinner.waitForDisplayed({ reverse: true });
|
||||
};
|
||||
|
||||
export const expect200 = async (app: Application) => {
|
||||
const tag = await app.client.$('.response-pane .pane__header .tag.bg-success');
|
||||
await tag.waitForDisplayed();
|
||||
await expectText(tag, '200 OK');
|
||||
};
|
||||
|
||||
export const expect401 = async (app: Application) => {
|
||||
const tag = await app.client.$('.response-pane .pane__header .tag.bg-warning');
|
||||
await tag.waitForDisplayed();
|
||||
await expectText(tag, '401 Unauthorized');
|
||||
};
|
||||
|
||||
export const getResponseViewer = async (app: Application) => {
|
||||
// app.client.react$('ResponseViewer') doesn't seem to work because ResponseViewer is not a PureComponent
|
||||
const codeEditor = await app.client.$('.response-pane .editor');
|
||||
await codeEditor.waitForDisplayed();
|
||||
return codeEditor;
|
||||
};
|
||||
|
||||
export const getTimelineViewer = async (app: Application) => {
|
||||
const codeEditor = await app.client.react$('ResponseTimelineViewer');
|
||||
await codeEditor.waitForDisplayed();
|
||||
return codeEditor;
|
||||
};
|
||||
|
||||
export const getCsvViewer = async (app: Application) => {
|
||||
const csvViewer = await app.client.react$('ResponseCSVViewer');
|
||||
await csvViewer.waitForDisplayed();
|
||||
return csvViewer;
|
||||
};
|
||||
|
||||
export const getPdfCanvas = async (app: Application) => {
|
||||
const pdfViewer = await app.client.react$('ResponsePDFViewer');
|
||||
await pdfViewer.waitForDisplayed();
|
||||
const canvas = await pdfViewer.$('.S-PDF-ID canvas');
|
||||
await canvas.waitForDisplayed();
|
||||
return canvas;
|
||||
};
|
||||
|
||||
export const clickRequestAuthDropdown = async (app: Application) => {
|
||||
await app.client
|
||||
.react$('AuthDropdown')
|
||||
.then(e => e.react$('DropdownButton'))
|
||||
.then(e => e.click());
|
||||
};
|
||||
|
||||
export const clickRequestAuthTab = async (app: Application) => {
|
||||
await app.client
|
||||
.react$('RequestPane')
|
||||
.then(e => e.$('#react-tabs-2'))
|
||||
.then(e => e.click());
|
||||
};
|
||||
|
||||
const basicAuthPause = 300;
|
||||
|
||||
export const clickBasicAuth = async (app: Application) => {
|
||||
await app.client
|
||||
.react$('AuthDropdown')
|
||||
.then(e => e.react$('DropdownItem', { props: { value: 'basic' } }))
|
||||
.then(e => e.click());
|
||||
|
||||
// Wait for basic auth to be enabled on the request
|
||||
await app.client.pause(basicAuthPause);
|
||||
};
|
||||
|
||||
export const expectNoAuthSelected = async (app: Application) => {
|
||||
const wrapper = await app.client.react$('RequestPane').then(e => e.react$('AuthWrapper'));
|
||||
await wrapper.waitForDisplayed();
|
||||
await expectText(wrapper, 'Select an auth type from above');
|
||||
};
|
||||
|
||||
export const typeBasicAuthUsernameAndPassword = async (app: Application, username: string, password: string, clear = false) => {
|
||||
const basicAuth = await app.client.react$('BasicAuth');
|
||||
await basicAuth.waitForExist();
|
||||
|
||||
const usernameEditor = await app.client.react$('OneLineEditor', {
|
||||
props: { id: 'username' },
|
||||
});
|
||||
await usernameEditor.waitForExist();
|
||||
await usernameEditor.click();
|
||||
|
||||
// Wait for the username editor field to update
|
||||
await app.client.pause(basicAuthPause);
|
||||
|
||||
if (clear) {
|
||||
await selectAll(app);
|
||||
}
|
||||
await usernameEditor.keys(username);
|
||||
|
||||
const passwordEditor = await app.client.react$('OneLineEditor', {
|
||||
props: { id: 'password' },
|
||||
});
|
||||
await passwordEditor.waitForExist();
|
||||
await passwordEditor.click();
|
||||
|
||||
// Allow username changes to persist and wait for
|
||||
// the password editor field to update
|
||||
await app.client.pause(basicAuthPause);
|
||||
|
||||
if (clear) {
|
||||
await selectAll(app);
|
||||
}
|
||||
await passwordEditor.keys(password);
|
||||
|
||||
// Allow password changes to persist
|
||||
await app.client.pause(basicAuthPause);
|
||||
};
|
||||
|
||||
export const toggleBasicAuthEnabled = async (app: Application) => {
|
||||
await app.client
|
||||
.react$('BasicAuth')
|
||||
.then(e => e.$('button#enabled'))
|
||||
.then(e => e.click());
|
||||
// Allow toggle to persist
|
||||
await app.client.pause(basicAuthPause);
|
||||
};
|
||||
|
||||
export const toggleBasicAuthEncoding = async (app: Application) => {
|
||||
await app.client
|
||||
.react$('BasicAuth')
|
||||
.then(e => e.$('button#use-iso-8859-1'))
|
||||
.then(e => e.click());
|
||||
|
||||
// Allow toggle to persist
|
||||
await app.client.pause(basicAuthPause);
|
||||
};
|
||||
|
||||
export const expectText = async (element: WebdriverIO.Element, text: string) => {
|
||||
await expect(element.getText()).resolves.toBe(text);
|
||||
};
|
||||
|
||||
export const expectContainsText = async (element: WebdriverIO.Element, text: string) => {
|
||||
await expect(element.getText()).resolves.toContain(text);
|
||||
};
|
||||
|
||||
export const expectNotContainsText = async (element: WebdriverIO.Element, text: string) => {
|
||||
await expect(element.getText()).resolves.not.toContain(text);
|
||||
};
|
||||
|
||||
export const clickTimelineTab = async (app: Application) => {
|
||||
await app.client
|
||||
.$('.response-pane')
|
||||
.then(e => e.$('#react-tabs-16'))
|
||||
.then(e => e.click());
|
||||
|
||||
// Wait until some text shows
|
||||
const codeEditor = await getTimelineViewer(app);
|
||||
await app.client.waitUntil(async () => Boolean(codeEditor.getText()));
|
||||
};
|
||||
|
||||
export const selectAll = async (app: Application) => {
|
||||
await app.client.keys(spectronKeys.mapAccelerator('CommandOrControl+A'));
|
||||
};
|
||||
|
||||
export const typeInResponseFilter = async (app: Application, filter: string) => {
|
||||
const toolbar = await app.client.$('.response-pane .editor__toolbar');
|
||||
await toolbar.waitForExist();
|
||||
await toolbar.click();
|
||||
|
||||
await toolbar.keys(filter);
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
|
||||
export const pageDisplayed = async (app: Application) => {
|
||||
await app.client.react$('WrapperDesign').then(e => e.waitForDisplayed());
|
||||
};
|
||||
|
||||
export const goToActivity = async (app: Application, activity: 'spec' | 'debug' | 'test' = 'spec') => {
|
||||
// NOTE: We shouldn't need to click the span.
|
||||
// TODO: Make the radio button group in the header keyboard accessible.
|
||||
const debugRadioInput = await app.client.$(`input[name="activity-toggle"][value=${activity}] ~ span`);
|
||||
|
||||
await debugRadioInput.click();
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
import findAsync from './find-async';
|
||||
|
||||
const findDropdownItemWithText = async (parent, text) => {
|
||||
let item;
|
||||
await parent.waitUntil(async () => {
|
||||
const items = await parent.react$$('DropdownItem');
|
||||
item = await findAsync(items, async i => (await i.getText()) === text);
|
||||
return !!item;
|
||||
});
|
||||
return item;
|
||||
};
|
||||
|
||||
export const clickDropdownItemByText = async (parent, text) => {
|
||||
const item = await findDropdownItemWithText(parent, text);
|
||||
await item.waitForDisplayed();
|
||||
await item.click();
|
||||
};
|
||||
|
||||
export const clickOpenDropdownItemByText = async (app, text) => {
|
||||
const item = await app.client
|
||||
.$('.dropdown__menu[aria-hidden=false]')
|
||||
.then(e => e.$(`button*=${text}`));
|
||||
await item.waitForDisplayed();
|
||||
await item.click();
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
export default async function findAsync(arr, asyncCallback) {
|
||||
const promises = arr.map(asyncCallback);
|
||||
const results = await Promise.all(promises);
|
||||
const index = results.findIndex(result => result);
|
||||
return arr[index];
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const loadFixture = async (fixturePath: string) => {
|
||||
const buffer = await fs.promises.readFile(path.join(__dirname, '..', 'fixtures', fixturePath));
|
||||
|
||||
const fixtureContent = buffer.toString('utf-8');
|
||||
|
||||
return fixtureContent;
|
||||
};
|
@ -1,97 +0,0 @@
|
||||
import faker from 'faker';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import * as debug from './debug';
|
||||
import * as design from './design';
|
||||
import * as dropdown from './dropdown';
|
||||
import * as modal from './modal';
|
||||
|
||||
export const documentListingShown = async app => {
|
||||
const item = await app.client.$('.document-listing');
|
||||
await item.waitForExist();
|
||||
};
|
||||
|
||||
export const expectDocumentWithTitle = async (app, title) => {
|
||||
await app.client.waitUntilTextExists('.document-listing__body', title);
|
||||
};
|
||||
|
||||
export const findCardWithTitle = async (app: Application, text: string) => {
|
||||
const cards = await app.client.react$$('Card');
|
||||
|
||||
for (const card of cards) {
|
||||
const cardTitle = await card.$('.title');
|
||||
|
||||
const title = await cardTitle.getText();
|
||||
if (title === text) {
|
||||
return card;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Card with title: ${text} not found`);
|
||||
};
|
||||
|
||||
export const cardHasBadge = async (card, text) => {
|
||||
const badge = await card.$('.header-item.card-badge');
|
||||
expect(await badge.getText()).toBe(text);
|
||||
};
|
||||
|
||||
export const openDocumentWithTitle = async (app: Application, text: string) => {
|
||||
const card = await findCardWithTitle(app, text);
|
||||
const cardBadge = await card.$('.header-item.card-badge');
|
||||
const isCollection = await cardBadge.getText() === 'Collection';
|
||||
await card.waitForDisplayed();
|
||||
await card.click();
|
||||
|
||||
if (isCollection) {
|
||||
await debug.pageDisplayed(app);
|
||||
} else {
|
||||
await design.goToActivity(app, 'spec');
|
||||
await design.pageDisplayed(app);
|
||||
}
|
||||
};
|
||||
|
||||
export const expectTotalDocuments = async (app, count) => {
|
||||
const label = count > 1 ? 'Documents' : 'Document';
|
||||
await app.client.waitUntilTextExists('.document-listing__footer', `${count} ${label}`);
|
||||
};
|
||||
|
||||
export const openWorkspaceCardDropdown = async card => {
|
||||
const dropdown = await card.react$('WorkspaceCardDropdown');
|
||||
await dropdown.click();
|
||||
};
|
||||
|
||||
const openCreateDropdown = async app => {
|
||||
const button = await app.client.$('button*=Create');
|
||||
await button.waitForClickable();
|
||||
await button.click();
|
||||
};
|
||||
|
||||
export const createNewCollection = async (app, prefix = 'coll') => {
|
||||
await openCreateDropdown(app);
|
||||
|
||||
await dropdown.clickDropdownItemByText(app.client, 'Request Collection');
|
||||
|
||||
const collectionName = `${prefix}-${faker.lorem.slug()}`;
|
||||
await modal.waitUntilOpened(app, { title: 'Create New Request Collection' });
|
||||
await modal.typeIntoModalInput(app, collectionName);
|
||||
await modal.clickModalFooterByText(app, 'Create');
|
||||
return collectionName;
|
||||
};
|
||||
|
||||
export const createNewDocument = async (app, prefix = 'doc') => {
|
||||
await openCreateDropdown(app);
|
||||
|
||||
await dropdown.clickDropdownItemByText(app.client, 'Design Document');
|
||||
|
||||
const documentName = `${prefix}-${faker.lorem.slug()}`;
|
||||
await modal.waitUntilOpened(app, { title: 'Create New Design Document' });
|
||||
await modal.typeIntoModalInput(app, documentName);
|
||||
await modal.clickModalFooterByText(app, 'Create');
|
||||
return documentName;
|
||||
};
|
||||
|
||||
export const importFromClipboard = async app => {
|
||||
await openCreateDropdown(app);
|
||||
|
||||
await dropdown.clickDropdownItemByText(app.client, 'Clipboard');
|
||||
};
|
@ -1,50 +0,0 @@
|
||||
export const migrationMessageShown = async app => {
|
||||
await app.client.waitUntilTextExists(
|
||||
'.onboarding__content__header h1',
|
||||
'Migrate from Insomnia Designer',
|
||||
);
|
||||
};
|
||||
|
||||
export const clickSkip = async app => {
|
||||
const button = await app.client.react$('MigrationBody').then(e => e.$('button=Skip for now'));
|
||||
await button.waitForClickable();
|
||||
await button.click();
|
||||
};
|
||||
|
||||
export const toggleOption = async (app, label) => {
|
||||
const toggle = await app.client
|
||||
.$('.onboarding__content__body')
|
||||
.then(e => e.react$('BooleanSetting', { props: { label } }));
|
||||
await toggle.waitForClickable();
|
||||
await toggle.click();
|
||||
};
|
||||
|
||||
const _getStartButton = async app => {
|
||||
return await app.client.react$('MigrationBody').then(e => e.$('button=Start Migration'));
|
||||
};
|
||||
|
||||
export const clickStart = async app => {
|
||||
const button = await _getStartButton(app);
|
||||
await button.waitForClickable();
|
||||
await button.click();
|
||||
};
|
||||
|
||||
export const ensureStartNotClickable = async app => {
|
||||
const button = await _getStartButton(app);
|
||||
await button.waitForClickable({ reverse: true });
|
||||
};
|
||||
|
||||
export const successMessageShown = async app => {
|
||||
await app.client.waitUntilTextExists(
|
||||
'.onboarding__content__body p strong',
|
||||
'Migrated successfully!',
|
||||
10000, // Wait 10 seconds for migration to complete
|
||||
);
|
||||
};
|
||||
|
||||
export const clickRestart = async app => {
|
||||
await app.client
|
||||
.react$('MigrationBody')
|
||||
.then(e => e.$('button=Restart Now'))
|
||||
.then(e => e.click());
|
||||
};
|
@ -1,45 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
|
||||
import findAsync from './find-async';
|
||||
|
||||
interface WaitUntilOpenedOptions {modalName?: string; title?: string}
|
||||
export const waitUntilOpened = async (app, { modalName, title }: WaitUntilOpenedOptions) => {
|
||||
if (modalName) {
|
||||
const modal = await app.client.react$(modalName);
|
||||
await modal.waitForDisplayed();
|
||||
} else {
|
||||
await app.client.waitUntilTextExists('div.modal__header__children', title);
|
||||
}
|
||||
};
|
||||
|
||||
export const close = async (app: Application, modalName?: string) => {
|
||||
let modal;
|
||||
if (modalName) {
|
||||
modal = await app.client.react$(modalName);
|
||||
} else {
|
||||
const modals = await app.client.react$$('Modal');
|
||||
modal = await findAsync(modals, m => m.isDisplayed());
|
||||
}
|
||||
|
||||
await modal.$('button.modal__close-btn').then(e => e.click());
|
||||
};
|
||||
|
||||
export const clickModalFooterByText = async (app, text) => {
|
||||
const btn = await app.client
|
||||
.$('.modal[aria-hidden=false] .modal__footer')
|
||||
.then(e => e.$(`button*=${text}`));
|
||||
await btn.waitForClickable();
|
||||
await btn.click();
|
||||
};
|
||||
|
||||
export const typeIntoModalInput = async (app, text) => {
|
||||
const input = await app.client.$('.modal input');
|
||||
await input.waitUntil(() => input.isFocused());
|
||||
await input.keys(text);
|
||||
};
|
||||
|
||||
export const selectModalOption = async (app, text) => {
|
||||
const select = await app.client.$('.modal select');
|
||||
|
||||
await select.selectByVisibleText(text);
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
const analyticsMessageShown = async app => {
|
||||
await app.client.waitUntilTextExists(
|
||||
'p strong',
|
||||
'Share Usage Analytics with Kong Inc',
|
||||
);
|
||||
};
|
||||
|
||||
const clickDontShare = async app => {
|
||||
await app.client
|
||||
.$('button=Don\'t share usage analytics')
|
||||
.then(e => e.click());
|
||||
};
|
||||
|
||||
export const skipOnboardingFlow = async app => {
|
||||
await analyticsMessageShown(app);
|
||||
await clickDontShare(app);
|
||||
};
|
@ -1,89 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
import { mapAccelerator } from 'spectron-keys';
|
||||
|
||||
import * as dropdown from './dropdown';
|
||||
import * as modal from './modal';
|
||||
import { clickTabByText } from './tabs';
|
||||
|
||||
export const openFromSettingsButton = async (app: Application) => {
|
||||
await (await app.client.$('[data-testid="settings-button"]')).click();
|
||||
|
||||
await modal.waitUntilOpened(app, { modalName: 'SettingsModal' });
|
||||
};
|
||||
|
||||
export const openWithKeyboardShortcut = async (app: Application) => {
|
||||
await app.client.keys(mapAccelerator('CommandOrControl+,'));
|
||||
|
||||
await modal.waitUntilOpened(app, { modalName: 'SettingsModal' });
|
||||
};
|
||||
|
||||
export const closeModal = async (app: Application) => {
|
||||
await modal.close(app, 'SettingsModal');
|
||||
};
|
||||
|
||||
export const goToPluginsTab = async (app: Application) => {
|
||||
// Click on the plugins tab
|
||||
await app.client.react$('SettingsModal').then(e => clickTabByText(e, 'Plugins'));
|
||||
|
||||
// Wait for the plugins component to show
|
||||
await app.client.react$('Plugins').then(e => e.waitForDisplayed());
|
||||
};
|
||||
|
||||
export const goToDataTab = async (app: Application) => {
|
||||
await app.client.react$('SettingsModal').then(e => clickTabByText(e, 'Data'));
|
||||
|
||||
await app.client.$('[data-testid="import-export-tab"]').then(e => e.waitForDisplayed());
|
||||
};
|
||||
|
||||
export const importFromClipboard = async (app: Application) => {
|
||||
const importExport = await app.client.$('[data-testid="import-export-tab"]');
|
||||
await importExport.waitForDisplayed();
|
||||
|
||||
await importExport.$('button*=Import Data').then(e => e.click());
|
||||
|
||||
await dropdown.clickOpenDropdownItemByText(app, 'From Clipboard');
|
||||
};
|
||||
|
||||
export const installPlugin = async (app, pluginName) => {
|
||||
const plugins = await app.client.react$('SettingsModal').then(e => e.react$('Plugins'));
|
||||
|
||||
// Find text input and install button
|
||||
const inputField = await plugins.$('form input[placeholder="npm-package-name"]');
|
||||
|
||||
// Click and wait for focus
|
||||
await inputField.waitForEnabled();
|
||||
await inputField.click();
|
||||
await inputField.waitUntil(() => inputField.isFocused());
|
||||
|
||||
// Type plugin name
|
||||
await app.client.keys(pluginName);
|
||||
|
||||
// Click install
|
||||
const installButton = await plugins.$('button=Install Plugin');
|
||||
await installButton.click();
|
||||
|
||||
// Button and field should disable
|
||||
await plugins.waitUntil(async () => {
|
||||
const buttonEnabled = await inputField.isEnabled();
|
||||
const fieldEnabled = await installButton.isEnabled();
|
||||
|
||||
return !buttonEnabled && !fieldEnabled;
|
||||
});
|
||||
|
||||
// Spinner should show
|
||||
await installButton.$('i.fa.fa-refresh.fa-spin').then(e => e.waitForDisplayed());
|
||||
|
||||
// Button and field should re-enable
|
||||
await plugins.waitUntil(
|
||||
async () => {
|
||||
const buttonEnabled = await inputField.isEnabled();
|
||||
const fieldEnabled = await installButton.isEnabled();
|
||||
|
||||
return buttonEnabled && fieldEnabled;
|
||||
},
|
||||
{ timeout: 10000, timeoutMsg: 'npm was slow to install the plugin' },
|
||||
);
|
||||
|
||||
// Plugin entry should exist in the table in the first row and second column
|
||||
await app.client.waitUntilTextExists('table tr:nth-of-type(1) td:nth-of-type(2)', pluginName);
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
export const clickTabByText = async (element, text) => {
|
||||
await element.$(`.react-tabs__tab=${text}`).then(e => e.click());
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
import { Application } from 'spectron';
|
||||
|
||||
export const waitUntilTextDisappears = async (app: Application, element: WebdriverIO.Element, text: string) => {
|
||||
await app.client.waitUntil(async () =>
|
||||
!(await element.getText()).includes(text)
|
||||
);
|
||||
};
|
3089
packages/insomnia-smoke-test/package-lock.json
generated
3089
packages/insomnia-smoke-test/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,16 +19,15 @@
|
||||
"clean": "tsc --build tsconfig.build.json --clean",
|
||||
"postclean": "rimraf dist",
|
||||
"build": "tsc --build tsconfig.build.json",
|
||||
"spectron:build": "cross-env BUNDLE=build xvfb-maybe jest --detectOpenHandles --testPathPattern core",
|
||||
"spectron:package": "cross-env BUNDLE=package xvfb-maybe jest --detectOpenHandles --testPathPattern core",
|
||||
"test:build": "xvfb-maybe cross-env BUNDLE=build playwright test",
|
||||
"test:package": "xvfb-maybe cross-env BUNDLE=package playwright test",
|
||||
"cli": "jest --detectOpenHandles --testPathPattern cli",
|
||||
"serve": "ts-node server/index.ts",
|
||||
"with-mock": "concurrently --names server,app --success first --kill-others \"npm run serve\"",
|
||||
"test:cli": "npm run with-mock \"npm run cli\"",
|
||||
"test:build": "npm run with-mock \"npm run spectron:build\"",
|
||||
"test:package": "npm run with-mock \"npm run spectron:package\""
|
||||
"test:cli": "npm run with-mock \"npm run cli\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.16.3",
|
||||
"@types/concurrently": "^6.0.1",
|
||||
"@types/express": "^4.17.11",
|
||||
"@types/faker": "^5.5.5",
|
||||
@ -45,9 +44,8 @@
|
||||
"mkdirp": "^1.0.4",
|
||||
"ramda": "^0.27.1",
|
||||
"ramda-adjunct": "^2.34.0",
|
||||
"spectron": "^13.0.0",
|
||||
"spectron-keys": "0.0.1",
|
||||
"ts-node": "^9.1.1",
|
||||
"uuid": "^8.3.0",
|
||||
"xvfb-maybe": "^0.2.1"
|
||||
}
|
||||
}
|
||||
|
15
packages/insomnia-smoke-test/playwright.config.ts
Normal file
15
packages/insomnia-smoke-test/playwright.config.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/* eslint-disable filenames/match-exported */
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: 'npm run serve',
|
||||
port: 4010,
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
timeout: process.env.CI ? 60 * 1000 : 20 * 1000,
|
||||
forbidOnly: !!process.env.CI,
|
||||
outputDir: 'screenshots',
|
||||
testDir: 'tests',
|
||||
};
|
||||
export default config;
|
45
packages/insomnia-smoke-test/playwright/paths.ts
Normal file
45
packages/insomnia-smoke-test/playwright/paths.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { exit } from 'process';
|
||||
import * as uuid from 'uuid';
|
||||
|
||||
export const loadFixture = async (fixturePath: string) => {
|
||||
const buffer = await fs.promises.readFile(path.join(__dirname, '..', 'fixtures', fixturePath));
|
||||
return buffer.toString('utf-8');
|
||||
};
|
||||
|
||||
export const randomDataPath = () => path.join(os.tmpdir(), 'insomnia-smoke-test', `${uuid.v4()}`);
|
||||
export const DESIGNER_DATA_PATH = path.join(__dirname, '..', 'fixtures', 'basic-designer');
|
||||
|
||||
const pathLookup = {
|
||||
win32: path.join('win-unpacked', 'Insomnia.exe'),
|
||||
darwin: path.join('mac', 'Insomnia.app', 'Contents', 'MacOS', 'Insomnia'),
|
||||
linux: path.join('linux-unpacked', 'insomnia'),
|
||||
};
|
||||
const insomniaBinary = path.join('dist', pathLookup[process.platform]);
|
||||
const electronBinary = path.join('node_modules', '.bin', process.platform === 'win32' ? 'electron.cmd' : 'electron');
|
||||
|
||||
export const executablePath = process.env.BUNDLE === 'package' ? insomniaBinary : electronBinary;
|
||||
export const mainPath = path.join('build', 'main.min.js');
|
||||
export const cwd = path.resolve(__dirname, '..', '..', 'insomnia-app');
|
||||
|
||||
const hasMainBeenBuilt = fs.existsSync(path.resolve(cwd, mainPath));
|
||||
const hasBinaryBeenBuilt = fs.existsSync(path.resolve(cwd, insomniaBinary));
|
||||
|
||||
// NOTE: guard against missing build artifacts
|
||||
if (process.env.BUNDLE !== 'package' && !hasMainBeenBuilt) {
|
||||
console.error(`ERROR: ${mainPath} not found at ${path.resolve(cwd, mainPath)}
|
||||
Have you run "npm run app-build:smoke"?`);
|
||||
exit(1);
|
||||
}
|
||||
if (process.env.BUNDLE === 'package' && !hasBinaryBeenBuilt) {
|
||||
console.error(`ERROR: ${insomniaBinary} not found at ${path.resolve(cwd, insomniaBinary)}
|
||||
Have you run "npm run app-package:smoke"?`);
|
||||
exit(1);
|
||||
}
|
||||
if (process.env.DEBUG) {
|
||||
console.log(`Using current working directory at ${cwd}`);
|
||||
console.log(`Using executablePath at ${executablePath}`);
|
||||
console.log(`Using mainPath at ${mainPath}`);
|
||||
}
|
80
packages/insomnia-smoke-test/tests/app.test.ts
Normal file
80
packages/insomnia-smoke-test/tests/app.test.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { PlaywrightWorkerArgs, test } from '@playwright/test';
|
||||
|
||||
import { cwd, DESIGNER_DATA_PATH, executablePath, loadFixture, mainPath, randomDataPath } from '../playwright/paths';
|
||||
|
||||
// NOTE: the DESIGNER_DATA_PATH argument is only used for overriding paths for migration testing,
|
||||
// if we remove migration from insomnia designer support this testing flow can be simplifed.
|
||||
type Playwright = PlaywrightWorkerArgs['playwright'];
|
||||
interface EnvOptions { INSOMNIA_DATA_PATH: string; DESIGNER_DATA_PATH?: string }
|
||||
|
||||
const newPage = async ({ playwright, options }: ({ playwright: Playwright; options: EnvOptions })) => {
|
||||
// NOTE: ensure the DESIGNER_DATA_PATH is ignored from process.env
|
||||
if (!options.DESIGNER_DATA_PATH) options.DESIGNER_DATA_PATH = 'doesnt-exist';
|
||||
const electronApp = await playwright._electron.launch({
|
||||
cwd,
|
||||
executablePath,
|
||||
args: process.env.BUNDLE === 'package' ? [] : [mainPath],
|
||||
env: {
|
||||
...process.env,
|
||||
...options,
|
||||
PLAYWRIGHT: 'true',
|
||||
},
|
||||
});
|
||||
await electronApp.waitForEvent('window');
|
||||
const page = await electronApp.firstWindow();
|
||||
// @TODO: Investigate why the app doesn't start without a reload with playwright in windows
|
||||
if (process.platform === 'win32') await page.reload();
|
||||
return { electronApp, page };
|
||||
};
|
||||
|
||||
test('can send requests', async ({ playwright }) => {
|
||||
const options = { INSOMNIA_DATA_PATH: randomDataPath() };
|
||||
const { page, electronApp } = await newPage({ playwright, options });
|
||||
await page.click('text=Don\'t share usage analytics');
|
||||
await page.click('text=Create');
|
||||
|
||||
const text = await loadFixture('smoke-test-collection.yaml');
|
||||
await electronApp.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);
|
||||
|
||||
await page.click('button:has-text("Clipboard")');
|
||||
await page.click('text=CollectionSmoke testsjust now');
|
||||
await page.click('button:has-text("GETsend JSON request")');
|
||||
await page.click('text=http://127.0.0.1:4010/pets/1Send >> button');
|
||||
await page.click('text=200 OK');
|
||||
await page.click('button:has-text("GETsends dummy.csv request and shows rich response")');
|
||||
await page.click('text=http://127.0.0.1:4010/file/dummy.csvSend >> button');
|
||||
await page.click('text=200 OK');
|
||||
await page.click('button:has-text("GETsends dummy.xml request and shows raw response")');
|
||||
await page.click('text=http://127.0.0.1:4010/file/dummy.xmlSend >> button');
|
||||
await page.click('text=200 OK');
|
||||
await page.click('button:has-text("GETsends dummy.pdf request and shows rich response")');
|
||||
await page.click('text=http://127.0.0.1:4010/file/dummy.pdfSend >> button');
|
||||
await page.click('text=200 OK');
|
||||
await page.click('button:has-text("GETsends request with basic authentication")');
|
||||
await page.click('text=http://127.0.0.1:4010/auth/basicSend >> button');
|
||||
await page.click('text=200 OK');
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test.describe.serial('given a designer and data directory', () => {
|
||||
const INSOMNIA_DATA_PATH = randomDataPath();
|
||||
|
||||
test('should complete migration dialog', async ({ playwright }) => {
|
||||
const options = { DESIGNER_DATA_PATH, INSOMNIA_DATA_PATH };
|
||||
const { page } = await newPage({ playwright, options });
|
||||
await page.click('text=Copy Workspaces');
|
||||
await page.click('text=Copy Plugins');
|
||||
await page.click('text=Copy Designer Application Settings');
|
||||
await page.click('text=Start Migration');
|
||||
await page.click('text=Migrated successfully!');
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('then on restart should see the migrated workspace', async ({ playwright }) => {
|
||||
const options = { DESIGNER_DATA_PATH, INSOMNIA_DATA_PATH };
|
||||
const { page } = await newPage({ playwright, options });
|
||||
await page.click('text=Don\'t share usage analytics');
|
||||
await page.click('text=BASIC-DESIGNER-FIXTURE');
|
||||
await page.close();
|
||||
});
|
||||
});
|
@ -6,7 +6,6 @@
|
||||
"rootDir": ".",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true, // this is required because spectron depends on electron but it is not locatable by typescript for the purpose of types
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": [
|
||||
|
@ -6,11 +6,11 @@
|
||||
},
|
||||
"include": [
|
||||
"__jest__",
|
||||
"tests",
|
||||
"cli",
|
||||
"core",
|
||||
"designer",
|
||||
"fixtures",
|
||||
"modules",
|
||||
"playwright.config.ts",
|
||||
"playwright",
|
||||
"server",
|
||||
"jest.config.js",
|
||||
".eslintrc.js"
|
||||
|
Loading…
Reference in New Issue
Block a user