web: add webdriverIO testing layer (#6959)
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* web: improve testing by adding test admin user via blueprint
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-02 15:40:39 +00:00
|
|
|
import type { Options } from "@wdio/types";
|
|
|
|
|
|
|
|
export const config: Options.Testrunner = {
|
|
|
|
//
|
|
|
|
// ====================
|
|
|
|
// Runner Configuration
|
|
|
|
// ====================
|
|
|
|
// WebdriverIO supports running e2e tests as well as unit and component tests.
|
|
|
|
runner: "local",
|
|
|
|
autoCompileOpts: {
|
|
|
|
autoCompile: true,
|
|
|
|
tsNodeOpts: {
|
|
|
|
project: "./tsconfig.json",
|
|
|
|
transpileOnly: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
//
|
|
|
|
// ==================
|
|
|
|
// Specify Test Files
|
|
|
|
// ==================
|
|
|
|
// Define which test specs should run. The pattern is relative to the directory
|
|
|
|
// of the configuration file being run.
|
|
|
|
//
|
|
|
|
// The specs are defined as an array of spec files (optionally using wildcards
|
|
|
|
// that will be expanded). The test for each spec file will be run in a separate
|
|
|
|
// worker process. In order to have a group of spec files run in the same worker
|
|
|
|
// process simply enclose them in an array within the specs array.
|
|
|
|
//
|
|
|
|
// If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script),
|
|
|
|
// then the current working directory is where your `package.json` resides, so `wdio`
|
|
|
|
// will be called from there.
|
|
|
|
//
|
|
|
|
specs: ["./test/specs/**/*.ts"],
|
|
|
|
// Patterns to exclude.
|
|
|
|
exclude: [
|
|
|
|
// 'path/to/excluded/files'
|
|
|
|
],
|
|
|
|
//
|
|
|
|
// ============
|
|
|
|
// Capabilities
|
|
|
|
// ============
|
|
|
|
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
|
|
|
|
// time. Depending on the number of capabilities, WebdriverIO launches several test
|
|
|
|
// sessions. Within your capabilities you can overwrite the spec and exclude options in
|
|
|
|
// order to group specific specs to a specific capability.
|
|
|
|
//
|
|
|
|
// First, you can define how many instances should be started at the same time. Let's
|
|
|
|
// say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
|
|
|
|
// set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
|
|
|
|
// files and you set maxInstances to 10, all spec files will get tested at the same time
|
|
|
|
// and 30 processes will get spawned. The property handles how many capabilities
|
|
|
|
// from the same test should run tests.
|
|
|
|
//
|
|
|
|
maxInstances: 1,
|
|
|
|
//
|
|
|
|
// If you have trouble getting all important capabilities together, check out the
|
|
|
|
// Sauce Labs platform configurator - a great tool to configure your capabilities:
|
|
|
|
// https://saucelabs.com/platform/platform-configurator
|
|
|
|
//
|
|
|
|
capabilities: [
|
|
|
|
{
|
|
|
|
"browserName": "chrome",
|
2024-01-02 23:46:28 +00:00
|
|
|
"wdio:chromedriverOptions": {
|
|
|
|
"binary": "./node_modules/.bin/chromedriver",
|
|
|
|
},
|
web: add webdriverIO testing layer (#6959)
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* web: improve testing by adding test admin user via blueprint
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-02 15:40:39 +00:00
|
|
|
"goog:chromeOptions": {
|
2024-01-02 23:46:28 +00:00
|
|
|
"args": ["--disable-infobars", "--window-size=1280,800"].concat(
|
web: add webdriverIO testing layer (#6959)
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* web: improve testing by adding test admin user via blueprint
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-02 15:40:39 +00:00
|
|
|
(function () {
|
|
|
|
return process.env.HEADLESS_CHROME === "1"
|
|
|
|
? [
|
|
|
|
"--headless",
|
|
|
|
"--no-sandbox",
|
|
|
|
"--disable-gpu",
|
|
|
|
"--disable-setuid-sandbox",
|
|
|
|
"--disable-dev-shm-usage",
|
|
|
|
]
|
|
|
|
: [];
|
|
|
|
})(),
|
|
|
|
),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
|
|
|
|
|
|
|
//
|
|
|
|
// ===================
|
|
|
|
// Test Configurations
|
|
|
|
// ===================
|
|
|
|
// Define all options that are relevant for the WebdriverIO instance here
|
|
|
|
//
|
|
|
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
web: Application wizard v2 with tests (#7004)
* A lot of comments about forms.
* Adding comments to the wizard.
* Broke out the text input into a single renderer. Still works as required.
* web: Legibility in the ApplicationForm.
This is a pretty good result. By using the LightDOM setting, this
provides the existing Authentik form manager with access to the
ak-form-horizontal-element components without having to do any
cross-border magic. It's not ideal, and it shows up just how badly
we've got patternfly splattered everywhere, but the actual results
are remarkable. The patterns for text, switch, radio, textarea,
file, and even select are smaller and easier here.
I'm still noodling on what an unspread search-select element would
look like. It's just dependency injection, so it ought to be as
straightforward as that.
* web: Marking down the start of the 'components' library.
* web: Baby steps
I become frustrated with my inability to make any progress on this project, so I decided to reach
for a tool that I consider highly reliable but also incredibly time-consuming and boring: test
driven development.
In this case, I wrote a story about how I wanted to see the first page rendered: just put the HTML
tag, completely unadorned, that will handle the first page of the wizard. Then, add an event handler
that will send the updated content to some parent object, since what we really want is to
orchestrate the state of the user's input with a centralized location. Then, rather than fiddling
with the attributes and properties of the various pages, I wanted them to be able to "look up" the
values they want, much as we'd expect a standalone form to be able to pull its values from the
server, so I added a context object that receives the update event and incorporates the new
knowledge about the state of the process into itself.
The result is surprisingly satisfying: the first page renders cleanly, displays the content that we
want, and as we fiddle with, we can *watch in real time* as the results of the context are updated
and retransmitted to all receiving objects. And the sending object gets the results so it
re-renders, but it ends up looking the same as it was before the render.
* Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug:
the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend
their events as standard `input` events, and that actually seems to work well; the browser is
decorating it with the right target, with the right `name` attribute, and since we have good
definitions of the `value` as a string (the real value of any search object is its UUID4), that
works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs),
and the latter has flags for "use the first one if it's the only one" and "allow the display of
keyless certificates."
Not sure why `state()` is blocking the transmission of typing information from the typed element
to the context handler, but it's a bug in the typechecker, and it's not a problem so far.
* Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug:
the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend
their events as standard `input` events, and that actually seems to work well; the browser is
decorating it with the right target, with the right `name` attribute, and since we have good
definitions of the `value` as a string (the real value of any search object is its UUID4), that
works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs),
and the latter has flags for "use the first one if it's the only one" and "allow the display of
keyless certificates."
Not sure why `state()` is blocking the transmission of typing information from the typed element
to the context handler, but it's a bug in the typechecker, and it's not a problem so far.
* web: tracked down that weirld bug with the radio.
Because radio inputs are actually multiples, the events handling for
radio is... wonky. If we want our `<ak-radio>` component to be a
unitary event dispatcher, saying "This is the element selected," we
needed to do more than what was currently being handled.
I've intercepted the events that we care about and have placed
them into a controller that dictates both the setting and the
re-render of the component. This makes it "controlled" (to use the
Angular/React/Vue) language and depends on Lit's reactiveElement
lifecycle to work, rather than trust the browser, but the browser's
experience with respect to the `<input type=radio` is pretty bad:
both input elements fire events, one for "losing selection" and
one for "gaining selection". That can be very confusing to handle,
so we funnel them down in our aggregate radio element to a single
event, "selection changed".
As a quality-of-life measure, I've also set the label to be
unselectable; this means that a click on the label will trigger the
selection event, and a long click will not disable selection or
confuse the selection event generator.
* web: now passing the precommit phase
* web: a HACK for Storybook to inject the "use light theme" flag into the body.
This isn't really a very good hack; what it does is say that every story is
responsible for hacking its theme into the parent. This is very annoying, but
it does mean that we can at least show our components in the best light.
* web: ak-application-wizard-authentication-by-oauth, and many fixes!
1. Fixed `eventEmitter` so that if the detail object is a scalar, it will not attempt to "objectify"
it. This was causing a bug where retrofitting the eventEmitter to some older components resulted
in a detail of "some" being translated into ['s', 'o', 'm', 'e']. Not what is wanted.
2. Removed the "transitional form" from the existing components; they had a two-step where the web
component class was just a wrapper around an independent rendering function. While this worked,
it was only to make the case that they *were* independent rendering objects and could be
supported with the right web component framework. We're halfway there now; the last step will be
to transform the horizontal-element and various input CSS into componentized CSS, the way
Patternfly-Elements is currently doing.
3. Fixed the `help` field so that it could take a string or a TemplateResult, and if the latter,
don't bother wrapping it in the helper text functionality; just let it be its own thing. This
supports the multi-line help of redirectURI as well as the `ak-utils-time-delta` capability.
4. Transform Oauth2ProviderForm to use the new components, to the best of our ability. Also used
the `provider = this.wizard.provider` and `provider = this.instance` syntax to make the render
function *completely portable*; it's the exact same text that is dropped into...
5. The complete `ak-application-wizard-authentication-by-oauth` component. They're so similar part
of me wonders if I could push them both out to a common reference, or a collection of common
references. Both components use the PropertyMapping and Sources, and both use the same
collection of searches (Crypto, Flow).
6. A Storybook for `ak-application-wizard-authentication-by-oauth`, showing the works working.
7. New mocks for `authorizationFlow`, `propertyMappings`, and `hasJWKs`.
This sequence has revealed a bug in the radio control. (It's always the radio control.) If the
default doesn't match the current setting, the radio control doesn't behave as expected; it won't
change when you fully expect that it should. I'll investigate how to harmonize those tomorrow.
* web: Converted our toggle groups to a more streamlined implementation.
* web: one more toggle group.
* initial api and schema
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* separate blueprint importer from yaml parsing
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* cleanup
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: Replace ad-hoc toggle control with ak-toggle-group
This commit replaces various ad-hoc implementations of the Patternfly Toggle Group HTML with a web
component that encapsulates all of the needed behavior and exposes a single API with a single event
handler, return the value of the option clicked.
The results are: Lots of visual clutter is eliminated. A single link of:
```
<div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.mode = ProxyMode.Proxy;
}}>
<span class="pf-c-toggle-group__text">${msg("Proxy")}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
```
Now looks like:
```
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
```
This also means that the three pages that used the Patternfly Toggle Group could eliminate all of
their Patternfly PFToggleGroup needs, as well as the `justify-content: center` extension, which also
eliminated the `css` import.
The savings aren't as spectacular as I'd hoped: removed 178 lines, but added 123; total savings 55
lines of code. I still count this a win: we need never write another toggle component again, and
any bugs, extensions or features we may want to add can be centralized or forked without risking the
whole edifice.
* web: minor code formatting issue.
* add new "must_created" state to blueprints to prevent overwriting objects
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: adding a storybook for the ak-toggle-group component
* Bugs found by CI/CD.
* web: Replace ad-hoc search for CryptoCertificateKeyPairs with ak-crypto-certeficate-search
This commit replaces various ad-hoc implementations of `search-select` for CryptoCertificateKeyPairs
with a web component that encapsulates all of the needed behavior and exposes a single API.
The results are: Lots of visual clutter is eliminated. A single search of:
```HTML
<ak-search-select
.fetchObjects=${async (query?: string): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return this.instance?.tlsVerification === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
```
Now looks like:
```HTML
<ak-crypto-certificate-search certificate=${this.instance?.tlsVerification}>
</ak-crypto-certificate-search>
```
There are three searches that do not require there to be a valid key with the certificate; these are
supported with the boolean property `nokey`; likewise, there is one search (in SAMLProviderForm)
that states that if there is no current certificate in the SAMLProvider and only one certificate can
be found in the Authentik database, use that one; this is supported with the boolean property
`singleton`.
These changes replace 382 lines of object-oriented invocations with 36 lines of declarative
configuration, and 98 lines for the class. Overall, the code for "find a crypto certificate" has
been reduced by 46%.
Suggestions for a better word than `singleton` are welcome!
* web: display tests for CryptoCertificateKeypair search
This adds a Storybook for the CryptoCertificateKeypair search, including
a mock fetch of the data. In the course of running the tests, we discovered
that including the SearchSelect _class_ won't include the customElement declaration
unless you include the whole file! Other bugs found: including the CSS from
Storybook is different from that of LitElement native, so much so that the
adapter needed to be included. FlowSearch had a similar bug. The problem
only manifests when building via Webpack (which Storybook uses) and not
Rollup, but we should support both in distribution.
* Fixed behavioral problem with the radio; the `if` there was
preventing the radio from reflecting the default correctly.
The observed behavior was that the radio wouldn't "activate"
until the item selected during the render pass was clicked on
first.
* Proxy Provider done.
* web: Tactical change. Put all the variants on the second page; it's
a longer list, but it's also easier to manage than all those
required sub-options.
* Rounding out the catalog.
* web: SAML Manual Configuration
Added a 'design document' that just kinda describes what I'm trying
to do, in case I don't get this done by Friday Aug 11, 2023.
I had two tables doing the same thing, so I merged them and then
wrote a few map/filters to specialize them for those two use cases.
Along the way I had to fiddle with the ESLint settings so that
underscore-prefixed unused variables would be ignored.
I cleaned up the visual appeal of the forms in the LDAP application.
I was copy/pasting the "handleProviderEvent" function, so I pulled
it out into ApplicationWizardProviderPageBase. Not so much a matter
of abstraction as just disliking that kind of duplication; it served
no purpose.
* Added SAML Story to Storybook.
* Web: This is coming together amazingly well. Like, almost too well.
* web: 80% of the way there
This commit includes the first three pages of the wizard, the
completion of the wizard framework with evented handling, and control
over progression.
Some shortcomings of this design have become evident: it isn't
possible to communicate between the steps' wrappers, as they are
POJOs without access to the context. An imperative decision-making
process has to be inserted in the orchestration layer,
which is kinda annoying.
But it looks good and it behaves correctly, to the extent that I've
given it behavior. It's an excellent foundation.
* Linting.
* web: application wizard
Found where the hook for form validity should go. Excellent! Now I just need to incorporate
that basic validation into the business logic and we're good to go.
* Turns out that was one layer too many; the topmost component was fine for
maintaining the context.
* It looks like my brilliant strategy has hit a snag.
The idea is simple. Let's start with this picture:
```
<application-wizard .steps=${[... a collection of step objects ...]}>
<wizard-main .steps=${(steps from above)}>
<application-current-panel>
<current-form>
```
- ApplicationWizard has a Context for the ApplicationProviderPair (or whatever it's going to be).
This context does not know about the steps; it just knows about: the "application" object, the
"provider" object, and a discriminator to know *which* provider the user has selected.
- ApplicationWizard has Steps that, among other things, provides Panels for:
- Application
- Pick Provider
- Configure Provider
- Submit ApplicationProviderPair to the back-end
- The WizardFrame renders the CurrentPanel for the CurrentStep
The CurrentPanel gets its data from the ApplicationWizard in the form of a Context. It then sends
messages (events) to ApplicationWizard about the contents of each field as the user is filling out
the form, so that the ApplicationWizard can record those in the ApplicationProviderPair for later
submission.
When a CurrentForm is valid, the ApplicationWizard updates the Steps object to show that the "Next
button" on the Wizard is now available.
In this way, the user can progress through the system. When they get to the last page, we can
provide in the ApplicationWizard with the means to submit the form and/or send the user back to
the page with the validation failure.
Problem: The context is being updated in real-time, which is triggering re-renders of the form. This
leads to focus problems as the fields that are not yet valid are triggering "focus grab" behavior.
This is a classic problem with "controlled" inputs. What we really want is for the CurrentPanel to
not re-render at all, but to behave like a normal, uncontrolled form, and let the browser do most of
the work. We still want the [Next] button to enable when the form is valid enough to permit that.
---
Other details: I've ripped out a lot of Jen's work, which is probably a mistake. It's still
preserved elsewhere. I've also cleaned up the various wizardly things to try and look organized.
It *looks* like it should work, it just... doesn't. Not yet.
* Late addition: I had an inspiration about how to reduce the way
reactivity broke focus by, basically, removing the reactivity and
managing the first-time-through lifecycle to prevent the update
from causing refocus. It works well! Now I just need to test it.
* This application fixes the bug with respect to the wizard-level context being updated incorrectly.
Understandings:
- To use uncontrolled inputs, which I prefer, the context object should not be a state or property
at the level of consumers; it should not automatically re-render with every keystroke, i.e. "The
React Way." We're using Web Components, [client-side
validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) exists on the
platform already, and live-validation is problematic for any number of reasons.
- The trade-off is that it is now necessary to re-render the target page of the wizard de-novo, but
that's not really as big a deal as it sounds. Lit is ready to do that... and then nothing else
until we request a change-of-page. Excellent.
- The top level context *must* be a state, but it's better if it's a state never actually used by
the top-level context container. The debate about whether or not to make that container a dumb one
(`<slot></slot>`) or to merge it with the top-level object continues; here, I've merged it with
the top-level wizard object, but that object does not refer to the state variable being managed in
its render pass, so changes to it do not cause a re-render of the whole wizard. The purpose of the
top-level page is to manage the *steps*, not the *content of any step*. A step may change
dynamically based on the content of a step, but that's the same thing as *which step*. Lesson:
always know what your state is *about*.
- Deep merging is a complex subject, but here it's appropriate to our needs.
* web: Application Wizard
This commit combines a working (but very unpolished) version of the Application Wizard with Jen's
code for the CoreTransactionApplicationRequest, resulting in a successful round trip.
It fixes a number of bugs with the way ContextProducer decorators were being processed, such that
they just weren't working with our current configuration (although they did work fine in Storybook);
consumers didn't need to be fixed.
It also *removes* the steps-aware context from the Wizard.
That *may* be a mistake. To re-iterate, the `WizardFrame` provides the chrome for a Wizard: the
button bar div, the breadcrumbs div, the header div, and it takes the steps object as its source of
truth for all of the content. The `WizardContent` part of the application has two parts: The
`WizardMain`, which wraps the frame and supplies the context for all the `WizardPanels`, and the
`WizardPanels` themselves, which are dependent on a context from `WizardMain` for the data that
populates each panel. YAGNI right now that the panels need to know anything about the steps, and the
`WizardMain` can just pass a fresh `.steps` object to the `WizardFrame` when they need updating.
Using props drilling may make more sense here.
It certainy does *not* make sense for the panels. They need to be renderable on-demand, and they
need to make sense of what they're rendering on-demand, so the function is
```
(panel code) => (context) => (rendered panel)
```
(Yes, that's curried notation. Deal.)
* This commit includes the first WDIO test for the ApplicationWizard. It doesn't do much right now, but
it does log in and navigate to the wizard successfully.
* web: completed test for single application, provided new programming language to make it easier to write tests.
* Almost there.
Missing: The validation is currently not working as expected, and I cannot get the backend
to give me meaningful data helping us "go back" to the field that wasn't valid. I really
don't want to put all the meaningful validation on the front-end; that's the road to -
perdition, the back-end must be usable by people less assiduous than we are.
Also: Need to make the button bar work better; maybe each panel can provide a custom button
bar if one is needed?
* web: Test harness
We have an end-to-end test harness that includes a trivially correct DSL for "This is what a user would do, do this":
```
const deleteProvider = (theSlug) => ([
["button", '>>>ak-sidebar-item a[href="#/core/providers"]'],
["deletebox", `>>>a[href="#/core/applications/${theSlug}"]`],
["button", '>>>ak-forms-delete-bulk button[slot="trigger"]'],
["button", '>>>ak-forms-delete-bulk div[role="dialog"] ak-spinner-button'],
]);
```
It's now possible to target individual sequences of events this way. With a little creativity, we could have standalone functions that take parameters for our calls and just do them, without too much struggle.
* web: Revised navigation
After working with the navigation for awhile, I realized that it's a poor map; what I really wanted was
a controller/view pair, where events flow up to the controller and then messages on "what to draw" flow
down to the view. It work quite well, and the wizard frame is smaller and smarter for it.
I've also moved the WDIO-driven tests into the 'tests' folder, because it (a) makes more sense to put
them there, and (b) it prevents any confusion about who's in charge of node_modules.
* web: Simplify, simplify, simplify
Sort-of.
This commit changes the way the "wizard step coordinator" layer works, giving the
wizard writer much more power over button bar. It still assumes there are only
three actions the wizard frame wants to commit: next, back, and close. This empowers
the steps themselves to re-arrange their buttons and describe the rules through which
transitions occur.
* web: resetting the form is not working yet...
I vehemently dislike the object-oriented "reset" command; every wizard should start with
an absolutely fresh copy of the data upon entry. Refactoring the wizard to re-build its
content from the inside is the correct way to go, but I don't have a good mental image
of how to make the ModalButton and the component it invokes interact cleanly, which
frustrates the hell out of me.
* web: reset
As I said, I greatly dislike having to be dependent upon "resets"; I prefer my
data to be de novo going into a "new" transaction. That said, we work with
what we've got; I've created an event generated by the wizard that says the
modal just closed; anything wrapping and implementing the wizard can then
capture that event and reset the data. I've also added a pair of functions
that create the two states (what step, what form data) anew, so that resetting
is as trivial as initializing (and is exactly the same, code-wise).
* web: Without error handling, this is complete, but I still need @BeryJu (Jens)
for help with the SAML Upload (it doesn't appear to be correctly handled?) and
the error handling.
* web: revise tests for wizard
This commit replaces the previous WDIO instance with a more formal and straightforward process using
the [pageobjects](https://martinfowler.com/bliki/PageObject.html). In this form, every major
component has its own test suite, and a test is a sequence of exercises of those components.
A test then becomes something as straightforward as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
expect(await UserLibraryPage.pageHeader).toHaveText("My Applications");
await UserLibraryPage.goToAdmin();
expect(await AdminOverviewPage.pageHeader).toHaveText("Welcome, ");
await AdminOverviewPage.openApplicationsListPage();
expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
ApplicationsListPage.startCreateApplicationWizard();
await ApplicationWizard.app.name.setValue(`Test application ${newId}`);
await ApplicationWizard.nextButton.click();
await (await ApplicationWizard.getProviderType("ldapprovider")).click();
await ApplicationWizard.nextButton.click();
await ApplicationWizard.ldap.setBindFlow("default-authentication-flow");
await ApplicationWizard.nextButton.click();
await expect(await ApplicationWizard.commitMessage).toHaveText(
"Your application has been saved"
);
```
Whether or not there's another layer of DSL in there or not, this is a pretty nice idiom for
maintaining tests.
* web: updating with forms and fixes for eslint complaints.
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* core/allow alternative postgres credentials
This commit allows the `dev-reset` command in the Makefile to pick up and use credentials from the
`.env` file if they are present, or fallback to the defaults provided if they are not. This is the
only place in the Makefile where the database credentials are used directly against postgresql
binaries. The syntax was tested with bash, zsh, and csh, and did not fail under those.
The `$${:-}` syntax is a combination of a Makefile idiom for "Pass a single `$` to the environment
where this command will be executed," and the shell expresion `${VARIABLE:-default}` means
"dereference the environment variable; if it is undefined, used the default value provided."
* Re-arrange sequence to avoid recursive make.
Nothing wrong with recursive make; it just wasn't essential
here. `migrate` is just a build target, not a task.
* Cleanup according to the Usage:
checkmake [options] <makefile>...
checkmake -h | --help
checkmake --version
checkmake --list-rules Makefile linting tool.
* core: added 'help' to the Makefile
* get postgres config from authentik config loader
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* don't set -x by default
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* sort help
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update help strings
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: test LDAP wizard sequence
* web: improve testing by adding test admin user via blueprint
* This commit continues the application wizard buildout. In this commit are the following changes:
- Added SCIM to the list of available providers
- Fixed ForwardProxy so that its mode is set correctly. (This is a special case in the committer;
I'm unhappy with that.)
- Fixed the commit messages so that:
- icons are set correctly (Success, Danger, Working)
- icons are colored correctly according to state
- commit message includes a `data-commit-state` field so tests can find it!
- Merged the application wizard tests into a single test pass
- Isolated common parts of the application wizard tests to reduce unnecessary repetition. All
application tests are the same until you reach the provider section anyway.
- Fixed the unit tests so they're finding the right error messages and are enabled to display them
correctly.
- Moved the test Form handlers into their own folder so they're not cluttering up the Pages folder.
* web: add radius to application wizard
This commit continues the application wizard buildout. In this commit are the following changes:
- Fixed a width-setting bug in the Makefile `make help` feature (i.e "automate that stuff!")
- Added Radius to the list of providers we can offer via the wizard
- Added `launchUrl` and `UI Settings` to features of the application page the wizard can find
- Changed 'SAML Manual Configuration' to just say "SAML Configuration"
- Modified `ak-form-group` to take and honor the `aria-label` property (which in turn makes it
easier to target specific forms with unit testing)
- Reduced the log level for wdio to 'warn'; 'info' was super-spammy and not helpful. It can be put
back with `--logLevel info` from the command line.
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: application wizard refactor & completion
This commit refactors the various components of the Wizard and ApplicationWizard, creating a much
more maintainable and satisfying Wizard experience for both developers (i.e, *me* and *Jens* so
far), and for the customer.
The Wizard base has been refactored into three components:
**AkWizardController**
The `AkWizardController` provides the event listenters for the wizard; it hooks them up, recevies the
events, and forwards them to the wizard. It unwraps the event objects and forwards the relevant
messages contained in the events. It knows of three event categories:
- Navigation requests (move to a different step)
- Update requests (the current step has updated the business content)
- Close requests (close or cancel the wizard).
**ak-wizard-frame**
The `ak-wizard-frame` is the ModalButton interface. It provides the Header, Breadcrumbs (nee`
"navigation block"), Buttons, and a DIV into which the main content is rendered.
**AkWizard**
`AkWizard` is an *incomplete* implementation of the wizard. It's meant to be inherited by a child
class, which will implement the rest. It extends `AKElement`. It provides the basic content needed,
such as steps, currentStep (as an index), an accessor for the step itself, an accessor for the
frame, and the interface to the `AkWizardController`.
**ApplicationWizard**
The `ApplicationWizard` itself has been refactored to accommodate these changes. It inherits from
`AkWizard` and provides the business logic for what to do when a form updates, some custom logic for
preventing moving through the wizard when the forms are incomplete, and a persistence layer for
filling out different providers in the same session. It's simplified a *lot*.
The types specified for `AkWizard` are pretty nifty, I think. I could wish the types being passed
via the custom events were more robust, but [strongly typed custom
events](https://github.com/lit/lit-element/issues/808) turn out to be quite the pain in the, er,
neck. As it is, the `precommit` pass did very good at preventing the worst disasters.
The steps themselves were re-written as objects so that they could take advantage of their `valid`
and `disabled` states and provide more meaningful buttons and labels. I think it's a solid
compromise, and it moved a lot of display logic out of the core `handleUpdate()` business method.
The tests, such as they are, are passing.
* Added comment describing new test.
* web: ensuring copy from `main` is canon
* web: fixes after merge
* web: laying the groundwork for future expansion
This commit is a hodge-podge of updates and changes to the web. Functional changes:
- Makefile: Fixed a bug in the `help` section that prevented the WIDTH from being accurately
calculated if `help` was included rather than in-lined.
- ESLint: Modified the "unused vars" rule so that variables starting with an underline are not
considered by the rule. This allows for elided variables in event handlers. It's not a perfect
solution-- a better one would be to use Typescript's function-specialization typing, but there are
too many places where we elide or ignore some variables in a function's usage that switching over
to specialization would be a huge lift.
- locale: It turns out, lit-locale does its own context management. We don't need to have a context
at all in this space, and that's one less listener we need to attach t othe DOM.
- ModalButton: A small thing, but using `nothing` instead of "html``" allows lit better control over
rendering and reduces the number of actual renders of the page.
- FormGroup: Provided a means to modify the aria-label, rather than stick with the just the word
"Details." Specializing this field will both help users of screen readers in the future, and will
allow test suites to find specific form groups now.
- RadioButton: provide a more consistent interface to the RadioButton. First, we dispatch the
events to the outside world, and we set the value locally so that the current `Form.ts` continues
to behave as expected. We also prevent the "button lost value" event from propagating; this
presents a unified select-like interface to users of the RadioButtonGroup. The current value
semantics are preserved; other clients of the RadioButton do not see a change in behavior.
- EventEmitter: If the custom event detail is *not* an object, do not use the object-like semantics
for forwarding it; just send it as-is.
- Comments: In the course of laying the groundwork for the application wizard, I throw a LOT of
comments into the code, describing APIs, interfaces, class and function signatures, to better
document the behavior inside and as signposts for future work.
* web: permit arrays to be sent in custom events without interpolation.
* actually use assignValue or rather serializeFieldRecursive
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: eslint & prettier fixes, plus small aesthetic differences.
* Restoring this file. Not sure where it disappears to.
* fix label in dark mode
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* SCIM Manuel -> SCIM
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* fix lint errors
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: better converter configuration, CSS repair, and forward-domain-proxy
1. Forward Domain Proxy. I wasn't sure if this method was appropriate for the wizard,
but Jens says it is. I've added it.
2. In the process of doing so, I decided that the Provider.converter field was overly
complexified; I tried too hard to reduce the number of functions I needed to define,
but in the process outsourced some of the logic of converting the Wizard's dataset
into a property typed request to the `commit` phase, which was inappropriate. All
of the logic about a provider, aside from its display, should be here with the code
that distinguishes between providers. This commit makes it so.
3. Small CSS fix: the fields inherited from the Proxy provider forms had some unexpected
CSS which was causing a bit of a weird indent. That has been rectified.
* web: running pre-commit after merge.
* web: ensure the applications wizard tests finish after current changes
* prettier has opinions.
* web: application wizard spit & polish
The "ApplicationWizardHint" now correctly uses the localstorage and allows the user to navigate back
and see the message after it's been hidden, so that it will always be available during the test
phase.
The ApplicationList's old "Create Application Form" button has been restored for the purposes of the
test phase.
The ApplicationWizard is now available on both the ApplicationList and ProviderList pages.
Tana and I discussed the microcopy, putting a stronger second-person "You can do..." twist onto the
language, to give the user the sense of empowerment.
The ShowHintController now has both "hide" and "show" operations, to support the hint restoration.
* web: updated storybook stories for the wizard, illustration how "a simple wizard" is configured in source code and tested with storybook.
* web: I hate getting spanked by prettier.
* web: sometimes I wish I had lower standards
Anyway, this was a very stupid bug, because by definition function
definition arguments don't have uses, they're being defined, not
implemented. Fixed, conf fixed to compensate, and consequences
conquered.
* move context from labs to main
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* Revert "move context from labs to main"
This reverts commit 3718ee69048966d26b1c357a7d2653fbb3ab613b.
* web: reify the data loop
I was very unhappy with the "update this dot-path" mechanism I was using earlier; it was hard
for me to read and understand what was happening, and I wrote the darned thing. I decided instead
to go with a hard substitution model; each phase of the wizard is responsible for updating the
*entire* payload, mostly by creating a new payload and substituting the field value associated
with the event.
On the receiver, we have to do that *again* to handle the swapping of providers when the user
chooses one and then another. It looks clunky, and it is, but it's *legible*; a junior dev
could understand what it's doing, and that's the goal.
* Revert "web: reify the data loop"
This reverts commit 09fedcacf02a90a021ce9e18c0eb4bec1ef48302.
* web: revert the 'lit' to 'lit-labs' for task and context.
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-18 19:43:37 +00:00
|
|
|
logLevel: "warn",
|
web: add webdriverIO testing layer (#6959)
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* web: improve testing by adding test admin user via blueprint
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-02 15:40:39 +00:00
|
|
|
//
|
|
|
|
// Set specific log levels per logger
|
|
|
|
// loggers:
|
|
|
|
// - webdriver, webdriverio
|
|
|
|
// - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
|
|
|
|
// - @wdio/mocha-framework, @wdio/jasmine-framework
|
|
|
|
// - @wdio/local-runner
|
|
|
|
// - @wdio/sumologic-reporter
|
|
|
|
// - @wdio/cli, @wdio/config, @wdio/utils
|
|
|
|
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
|
|
|
// logLevels: {
|
|
|
|
// webdriver: 'info',
|
|
|
|
// '@wdio/appium-service': 'info'
|
|
|
|
// },
|
|
|
|
//
|
|
|
|
// If you only want to run your tests until a specific amount of tests have failed use
|
|
|
|
// bail (default is 0 - don't bail, run all tests).
|
|
|
|
bail: 0,
|
|
|
|
//
|
|
|
|
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
|
|
|
|
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
|
|
|
|
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
|
|
|
|
// gets prepended directly.
|
|
|
|
baseUrl: "http://localhost",
|
|
|
|
//
|
|
|
|
// Default timeout for all waitFor* commands.
|
|
|
|
waitforTimeout: 10000,
|
|
|
|
//
|
|
|
|
// Default timeout in milliseconds for request
|
|
|
|
// if browser driver or grid doesn't send response
|
|
|
|
connectionRetryTimeout: 120000,
|
|
|
|
//
|
|
|
|
// Default request retries count
|
|
|
|
connectionRetryCount: 3,
|
|
|
|
//
|
|
|
|
// Test runner services
|
|
|
|
// Services take over a specific job you don't want to take care of. They enhance
|
|
|
|
// your test setup with almost no effort. Unlike plugins, they don't add new
|
|
|
|
// commands. Instead, they hook themselves up into the test process.
|
|
|
|
// services: [],
|
|
|
|
//
|
|
|
|
// Framework you want to run your specs with.
|
|
|
|
// The following are supported: Mocha, Jasmine, and Cucumber
|
|
|
|
// see also: https://webdriver.io/docs/frameworks
|
|
|
|
//
|
|
|
|
// Make sure you have the wdio adapter package for the specific framework installed
|
|
|
|
// before running any tests.
|
|
|
|
framework: "mocha",
|
|
|
|
//
|
|
|
|
// The number of times to retry the entire specfile when it fails as a whole
|
|
|
|
// specFileRetries: 1,
|
|
|
|
//
|
|
|
|
// Delay in seconds between the spec file retry attempts
|
|
|
|
// specFileRetriesDelay: 0,
|
|
|
|
//
|
|
|
|
// Whether or not retried spec files should be retried immediately or deferred to the end of the queue
|
|
|
|
// specFileRetriesDeferred: false,
|
|
|
|
//
|
|
|
|
// Test reporter for stdout.
|
|
|
|
// The only one supported by default is 'dot'
|
|
|
|
// see also: https://webdriver.io/docs/dot-reporter
|
|
|
|
reporters: ["spec"],
|
|
|
|
|
|
|
|
//
|
|
|
|
// Options to be passed to Mocha.
|
|
|
|
// See the full list at http://mochajs.org/
|
|
|
|
mochaOpts: {
|
|
|
|
ui: "bdd",
|
|
|
|
timeout: 60000,
|
|
|
|
},
|
web: Application wizard v2 with tests (#7004)
* A lot of comments about forms.
* Adding comments to the wizard.
* Broke out the text input into a single renderer. Still works as required.
* web: Legibility in the ApplicationForm.
This is a pretty good result. By using the LightDOM setting, this
provides the existing Authentik form manager with access to the
ak-form-horizontal-element components without having to do any
cross-border magic. It's not ideal, and it shows up just how badly
we've got patternfly splattered everywhere, but the actual results
are remarkable. The patterns for text, switch, radio, textarea,
file, and even select are smaller and easier here.
I'm still noodling on what an unspread search-select element would
look like. It's just dependency injection, so it ought to be as
straightforward as that.
* web: Marking down the start of the 'components' library.
* web: Baby steps
I become frustrated with my inability to make any progress on this project, so I decided to reach
for a tool that I consider highly reliable but also incredibly time-consuming and boring: test
driven development.
In this case, I wrote a story about how I wanted to see the first page rendered: just put the HTML
tag, completely unadorned, that will handle the first page of the wizard. Then, add an event handler
that will send the updated content to some parent object, since what we really want is to
orchestrate the state of the user's input with a centralized location. Then, rather than fiddling
with the attributes and properties of the various pages, I wanted them to be able to "look up" the
values they want, much as we'd expect a standalone form to be able to pull its values from the
server, so I added a context object that receives the update event and incorporates the new
knowledge about the state of the process into itself.
The result is surprisingly satisfying: the first page renders cleanly, displays the content that we
want, and as we fiddle with, we can *watch in real time* as the results of the context are updated
and retransmitted to all receiving objects. And the sending object gets the results so it
re-renders, but it ends up looking the same as it was before the render.
* Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug:
the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend
their events as standard `input` events, and that actually seems to work well; the browser is
decorating it with the right target, with the right `name` attribute, and since we have good
definitions of the `value` as a string (the real value of any search object is its UUID4), that
works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs),
and the latter has flags for "use the first one if it's the only one" and "allow the display of
keyless certificates."
Not sure why `state()` is blocking the transmission of typing information from the typed element
to the context handler, but it's a bug in the typechecker, and it's not a problem so far.
* Now, it's starting to look like a complete package. The LDAP method is working, but there is a bug:
the radio is sending the wrong value !?!?!?. Track that down, dammit. The search wrappers now resend
their events as standard `input` events, and that actually seems to work well; the browser is
decorating it with the right target, with the right `name` attribute, and since we have good
definitions of the `value` as a string (the real value of any search object is its UUID4), that
works quite well. Added search wrappers for CoreGroup and CryptoCertificate (CertificateKeyPairs),
and the latter has flags for "use the first one if it's the only one" and "allow the display of
keyless certificates."
Not sure why `state()` is blocking the transmission of typing information from the typed element
to the context handler, but it's a bug in the typechecker, and it's not a problem so far.
* web: tracked down that weirld bug with the radio.
Because radio inputs are actually multiples, the events handling for
radio is... wonky. If we want our `<ak-radio>` component to be a
unitary event dispatcher, saying "This is the element selected," we
needed to do more than what was currently being handled.
I've intercepted the events that we care about and have placed
them into a controller that dictates both the setting and the
re-render of the component. This makes it "controlled" (to use the
Angular/React/Vue) language and depends on Lit's reactiveElement
lifecycle to work, rather than trust the browser, but the browser's
experience with respect to the `<input type=radio` is pretty bad:
both input elements fire events, one for "losing selection" and
one for "gaining selection". That can be very confusing to handle,
so we funnel them down in our aggregate radio element to a single
event, "selection changed".
As a quality-of-life measure, I've also set the label to be
unselectable; this means that a click on the label will trigger the
selection event, and a long click will not disable selection or
confuse the selection event generator.
* web: now passing the precommit phase
* web: a HACK for Storybook to inject the "use light theme" flag into the body.
This isn't really a very good hack; what it does is say that every story is
responsible for hacking its theme into the parent. This is very annoying, but
it does mean that we can at least show our components in the best light.
* web: ak-application-wizard-authentication-by-oauth, and many fixes!
1. Fixed `eventEmitter` so that if the detail object is a scalar, it will not attempt to "objectify"
it. This was causing a bug where retrofitting the eventEmitter to some older components resulted
in a detail of "some" being translated into ['s', 'o', 'm', 'e']. Not what is wanted.
2. Removed the "transitional form" from the existing components; they had a two-step where the web
component class was just a wrapper around an independent rendering function. While this worked,
it was only to make the case that they *were* independent rendering objects and could be
supported with the right web component framework. We're halfway there now; the last step will be
to transform the horizontal-element and various input CSS into componentized CSS, the way
Patternfly-Elements is currently doing.
3. Fixed the `help` field so that it could take a string or a TemplateResult, and if the latter,
don't bother wrapping it in the helper text functionality; just let it be its own thing. This
supports the multi-line help of redirectURI as well as the `ak-utils-time-delta` capability.
4. Transform Oauth2ProviderForm to use the new components, to the best of our ability. Also used
the `provider = this.wizard.provider` and `provider = this.instance` syntax to make the render
function *completely portable*; it's the exact same text that is dropped into...
5. The complete `ak-application-wizard-authentication-by-oauth` component. They're so similar part
of me wonders if I could push them both out to a common reference, or a collection of common
references. Both components use the PropertyMapping and Sources, and both use the same
collection of searches (Crypto, Flow).
6. A Storybook for `ak-application-wizard-authentication-by-oauth`, showing the works working.
7. New mocks for `authorizationFlow`, `propertyMappings`, and `hasJWKs`.
This sequence has revealed a bug in the radio control. (It's always the radio control.) If the
default doesn't match the current setting, the radio control doesn't behave as expected; it won't
change when you fully expect that it should. I'll investigate how to harmonize those tomorrow.
* web: Converted our toggle groups to a more streamlined implementation.
* web: one more toggle group.
* initial api and schema
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* separate blueprint importer from yaml parsing
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* cleanup
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: Replace ad-hoc toggle control with ak-toggle-group
This commit replaces various ad-hoc implementations of the Patternfly Toggle Group HTML with a web
component that encapsulates all of the needed behavior and exposes a single API with a single event
handler, return the value of the option clicked.
The results are: Lots of visual clutter is eliminated. A single link of:
```
<div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.mode === ProxyMode.Proxy
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.mode = ProxyMode.Proxy;
}}>
<span class="pf-c-toggle-group__text">${msg("Proxy")}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
```
Now looks like:
```
<option value=${ProxyMode.Proxy}>${msg("Proxy")}</option>
```
This also means that the three pages that used the Patternfly Toggle Group could eliminate all of
their Patternfly PFToggleGroup needs, as well as the `justify-content: center` extension, which also
eliminated the `css` import.
The savings aren't as spectacular as I'd hoped: removed 178 lines, but added 123; total savings 55
lines of code. I still count this a win: we need never write another toggle component again, and
any bugs, extensions or features we may want to add can be centralized or forked without risking the
whole edifice.
* web: minor code formatting issue.
* add new "must_created" state to blueprints to prevent overwriting objects
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: adding a storybook for the ak-toggle-group component
* Bugs found by CI/CD.
* web: Replace ad-hoc search for CryptoCertificateKeyPairs with ak-crypto-certeficate-search
This commit replaces various ad-hoc implementations of `search-select` for CryptoCertificateKeyPairs
with a web component that encapsulates all of the needed behavior and exposes a single API.
The results are: Lots of visual clutter is eliminated. A single search of:
```HTML
<ak-search-select
.fetchObjects=${async (query?: string): Promise<CertificateKeyPair[]> => {
const args: CryptoCertificatekeypairsListRequest = {
ordering: "name",
hasKey: true,
includeDetails: false,
};
if (query !== undefined) {
args.search = query;
}
const certificates = await new CryptoApi(
DEFAULT_CONFIG,
).cryptoCertificatekeypairsList(args);
return certificates.results;
}}
.renderElement=${(item: CertificateKeyPair): string => {
return item.name;
}}
.value=${(item: CertificateKeyPair | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: CertificateKeyPair): boolean => {
return this.instance?.tlsVerification === item.pk;
}}
?blankable=${true}
>
</ak-search-select>
```
Now looks like:
```HTML
<ak-crypto-certificate-search certificate=${this.instance?.tlsVerification}>
</ak-crypto-certificate-search>
```
There are three searches that do not require there to be a valid key with the certificate; these are
supported with the boolean property `nokey`; likewise, there is one search (in SAMLProviderForm)
that states that if there is no current certificate in the SAMLProvider and only one certificate can
be found in the Authentik database, use that one; this is supported with the boolean property
`singleton`.
These changes replace 382 lines of object-oriented invocations with 36 lines of declarative
configuration, and 98 lines for the class. Overall, the code for "find a crypto certificate" has
been reduced by 46%.
Suggestions for a better word than `singleton` are welcome!
* web: display tests for CryptoCertificateKeypair search
This adds a Storybook for the CryptoCertificateKeypair search, including
a mock fetch of the data. In the course of running the tests, we discovered
that including the SearchSelect _class_ won't include the customElement declaration
unless you include the whole file! Other bugs found: including the CSS from
Storybook is different from that of LitElement native, so much so that the
adapter needed to be included. FlowSearch had a similar bug. The problem
only manifests when building via Webpack (which Storybook uses) and not
Rollup, but we should support both in distribution.
* Fixed behavioral problem with the radio; the `if` there was
preventing the radio from reflecting the default correctly.
The observed behavior was that the radio wouldn't "activate"
until the item selected during the render pass was clicked on
first.
* Proxy Provider done.
* web: Tactical change. Put all the variants on the second page; it's
a longer list, but it's also easier to manage than all those
required sub-options.
* Rounding out the catalog.
* web: SAML Manual Configuration
Added a 'design document' that just kinda describes what I'm trying
to do, in case I don't get this done by Friday Aug 11, 2023.
I had two tables doing the same thing, so I merged them and then
wrote a few map/filters to specialize them for those two use cases.
Along the way I had to fiddle with the ESLint settings so that
underscore-prefixed unused variables would be ignored.
I cleaned up the visual appeal of the forms in the LDAP application.
I was copy/pasting the "handleProviderEvent" function, so I pulled
it out into ApplicationWizardProviderPageBase. Not so much a matter
of abstraction as just disliking that kind of duplication; it served
no purpose.
* Added SAML Story to Storybook.
* Web: This is coming together amazingly well. Like, almost too well.
* web: 80% of the way there
This commit includes the first three pages of the wizard, the
completion of the wizard framework with evented handling, and control
over progression.
Some shortcomings of this design have become evident: it isn't
possible to communicate between the steps' wrappers, as they are
POJOs without access to the context. An imperative decision-making
process has to be inserted in the orchestration layer,
which is kinda annoying.
But it looks good and it behaves correctly, to the extent that I've
given it behavior. It's an excellent foundation.
* Linting.
* web: application wizard
Found where the hook for form validity should go. Excellent! Now I just need to incorporate
that basic validation into the business logic and we're good to go.
* Turns out that was one layer too many; the topmost component was fine for
maintaining the context.
* It looks like my brilliant strategy has hit a snag.
The idea is simple. Let's start with this picture:
```
<application-wizard .steps=${[... a collection of step objects ...]}>
<wizard-main .steps=${(steps from above)}>
<application-current-panel>
<current-form>
```
- ApplicationWizard has a Context for the ApplicationProviderPair (or whatever it's going to be).
This context does not know about the steps; it just knows about: the "application" object, the
"provider" object, and a discriminator to know *which* provider the user has selected.
- ApplicationWizard has Steps that, among other things, provides Panels for:
- Application
- Pick Provider
- Configure Provider
- Submit ApplicationProviderPair to the back-end
- The WizardFrame renders the CurrentPanel for the CurrentStep
The CurrentPanel gets its data from the ApplicationWizard in the form of a Context. It then sends
messages (events) to ApplicationWizard about the contents of each field as the user is filling out
the form, so that the ApplicationWizard can record those in the ApplicationProviderPair for later
submission.
When a CurrentForm is valid, the ApplicationWizard updates the Steps object to show that the "Next
button" on the Wizard is now available.
In this way, the user can progress through the system. When they get to the last page, we can
provide in the ApplicationWizard with the means to submit the form and/or send the user back to
the page with the validation failure.
Problem: The context is being updated in real-time, which is triggering re-renders of the form. This
leads to focus problems as the fields that are not yet valid are triggering "focus grab" behavior.
This is a classic problem with "controlled" inputs. What we really want is for the CurrentPanel to
not re-render at all, but to behave like a normal, uncontrolled form, and let the browser do most of
the work. We still want the [Next] button to enable when the form is valid enough to permit that.
---
Other details: I've ripped out a lot of Jen's work, which is probably a mistake. It's still
preserved elsewhere. I've also cleaned up the various wizardly things to try and look organized.
It *looks* like it should work, it just... doesn't. Not yet.
* Late addition: I had an inspiration about how to reduce the way
reactivity broke focus by, basically, removing the reactivity and
managing the first-time-through lifecycle to prevent the update
from causing refocus. It works well! Now I just need to test it.
* This application fixes the bug with respect to the wizard-level context being updated incorrectly.
Understandings:
- To use uncontrolled inputs, which I prefer, the context object should not be a state or property
at the level of consumers; it should not automatically re-render with every keystroke, i.e. "The
React Way." We're using Web Components, [client-side
validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) exists on the
platform already, and live-validation is problematic for any number of reasons.
- The trade-off is that it is now necessary to re-render the target page of the wizard de-novo, but
that's not really as big a deal as it sounds. Lit is ready to do that... and then nothing else
until we request a change-of-page. Excellent.
- The top level context *must* be a state, but it's better if it's a state never actually used by
the top-level context container. The debate about whether or not to make that container a dumb one
(`<slot></slot>`) or to merge it with the top-level object continues; here, I've merged it with
the top-level wizard object, but that object does not refer to the state variable being managed in
its render pass, so changes to it do not cause a re-render of the whole wizard. The purpose of the
top-level page is to manage the *steps*, not the *content of any step*. A step may change
dynamically based on the content of a step, but that's the same thing as *which step*. Lesson:
always know what your state is *about*.
- Deep merging is a complex subject, but here it's appropriate to our needs.
* web: Application Wizard
This commit combines a working (but very unpolished) version of the Application Wizard with Jen's
code for the CoreTransactionApplicationRequest, resulting in a successful round trip.
It fixes a number of bugs with the way ContextProducer decorators were being processed, such that
they just weren't working with our current configuration (although they did work fine in Storybook);
consumers didn't need to be fixed.
It also *removes* the steps-aware context from the Wizard.
That *may* be a mistake. To re-iterate, the `WizardFrame` provides the chrome for a Wizard: the
button bar div, the breadcrumbs div, the header div, and it takes the steps object as its source of
truth for all of the content. The `WizardContent` part of the application has two parts: The
`WizardMain`, which wraps the frame and supplies the context for all the `WizardPanels`, and the
`WizardPanels` themselves, which are dependent on a context from `WizardMain` for the data that
populates each panel. YAGNI right now that the panels need to know anything about the steps, and the
`WizardMain` can just pass a fresh `.steps` object to the `WizardFrame` when they need updating.
Using props drilling may make more sense here.
It certainy does *not* make sense for the panels. They need to be renderable on-demand, and they
need to make sense of what they're rendering on-demand, so the function is
```
(panel code) => (context) => (rendered panel)
```
(Yes, that's curried notation. Deal.)
* This commit includes the first WDIO test for the ApplicationWizard. It doesn't do much right now, but
it does log in and navigate to the wizard successfully.
* web: completed test for single application, provided new programming language to make it easier to write tests.
* Almost there.
Missing: The validation is currently not working as expected, and I cannot get the backend
to give me meaningful data helping us "go back" to the field that wasn't valid. I really
don't want to put all the meaningful validation on the front-end; that's the road to -
perdition, the back-end must be usable by people less assiduous than we are.
Also: Need to make the button bar work better; maybe each panel can provide a custom button
bar if one is needed?
* web: Test harness
We have an end-to-end test harness that includes a trivially correct DSL for "This is what a user would do, do this":
```
const deleteProvider = (theSlug) => ([
["button", '>>>ak-sidebar-item a[href="#/core/providers"]'],
["deletebox", `>>>a[href="#/core/applications/${theSlug}"]`],
["button", '>>>ak-forms-delete-bulk button[slot="trigger"]'],
["button", '>>>ak-forms-delete-bulk div[role="dialog"] ak-spinner-button'],
]);
```
It's now possible to target individual sequences of events this way. With a little creativity, we could have standalone functions that take parameters for our calls and just do them, without too much struggle.
* web: Revised navigation
After working with the navigation for awhile, I realized that it's a poor map; what I really wanted was
a controller/view pair, where events flow up to the controller and then messages on "what to draw" flow
down to the view. It work quite well, and the wizard frame is smaller and smarter for it.
I've also moved the WDIO-driven tests into the 'tests' folder, because it (a) makes more sense to put
them there, and (b) it prevents any confusion about who's in charge of node_modules.
* web: Simplify, simplify, simplify
Sort-of.
This commit changes the way the "wizard step coordinator" layer works, giving the
wizard writer much more power over button bar. It still assumes there are only
three actions the wizard frame wants to commit: next, back, and close. This empowers
the steps themselves to re-arrange their buttons and describe the rules through which
transitions occur.
* web: resetting the form is not working yet...
I vehemently dislike the object-oriented "reset" command; every wizard should start with
an absolutely fresh copy of the data upon entry. Refactoring the wizard to re-build its
content from the inside is the correct way to go, but I don't have a good mental image
of how to make the ModalButton and the component it invokes interact cleanly, which
frustrates the hell out of me.
* web: reset
As I said, I greatly dislike having to be dependent upon "resets"; I prefer my
data to be de novo going into a "new" transaction. That said, we work with
what we've got; I've created an event generated by the wizard that says the
modal just closed; anything wrapping and implementing the wizard can then
capture that event and reset the data. I've also added a pair of functions
that create the two states (what step, what form data) anew, so that resetting
is as trivial as initializing (and is exactly the same, code-wise).
* web: Without error handling, this is complete, but I still need @BeryJu (Jens)
for help with the SAML Upload (it doesn't appear to be correctly handled?) and
the error handling.
* web: revise tests for wizard
This commit replaces the previous WDIO instance with a more formal and straightforward process using
the [pageobjects](https://martinfowler.com/bliki/PageObject.html). In this form, every major
component has its own test suite, and a test is a sequence of exercises of those components.
A test then becomes something as straightforward as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
expect(await UserLibraryPage.pageHeader).toHaveText("My Applications");
await UserLibraryPage.goToAdmin();
expect(await AdminOverviewPage.pageHeader).toHaveText("Welcome, ");
await AdminOverviewPage.openApplicationsListPage();
expect(await ApplicationsListPage.pageHeader).toHaveText("Applications");
ApplicationsListPage.startCreateApplicationWizard();
await ApplicationWizard.app.name.setValue(`Test application ${newId}`);
await ApplicationWizard.nextButton.click();
await (await ApplicationWizard.getProviderType("ldapprovider")).click();
await ApplicationWizard.nextButton.click();
await ApplicationWizard.ldap.setBindFlow("default-authentication-flow");
await ApplicationWizard.nextButton.click();
await expect(await ApplicationWizard.commitMessage).toHaveText(
"Your application has been saved"
);
```
Whether or not there's another layer of DSL in there or not, this is a pretty nice idiom for
maintaining tests.
* web: updating with forms and fixes for eslint complaints.
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* core/allow alternative postgres credentials
This commit allows the `dev-reset` command in the Makefile to pick up and use credentials from the
`.env` file if they are present, or fallback to the defaults provided if they are not. This is the
only place in the Makefile where the database credentials are used directly against postgresql
binaries. The syntax was tested with bash, zsh, and csh, and did not fail under those.
The `$${:-}` syntax is a combination of a Makefile idiom for "Pass a single `$` to the environment
where this command will be executed," and the shell expresion `${VARIABLE:-default}` means
"dereference the environment variable; if it is undefined, used the default value provided."
* Re-arrange sequence to avoid recursive make.
Nothing wrong with recursive make; it just wasn't essential
here. `migrate` is just a build target, not a task.
* Cleanup according to the Usage:
checkmake [options] <makefile>...
checkmake -h | --help
checkmake --version
checkmake --list-rules Makefile linting tool.
* core: added 'help' to the Makefile
* get postgres config from authentik config loader
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* don't set -x by default
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* sort help
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update help strings
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: test LDAP wizard sequence
* web: improve testing by adding test admin user via blueprint
* This commit continues the application wizard buildout. In this commit are the following changes:
- Added SCIM to the list of available providers
- Fixed ForwardProxy so that its mode is set correctly. (This is a special case in the committer;
I'm unhappy with that.)
- Fixed the commit messages so that:
- icons are set correctly (Success, Danger, Working)
- icons are colored correctly according to state
- commit message includes a `data-commit-state` field so tests can find it!
- Merged the application wizard tests into a single test pass
- Isolated common parts of the application wizard tests to reduce unnecessary repetition. All
application tests are the same until you reach the provider section anyway.
- Fixed the unit tests so they're finding the right error messages and are enabled to display them
correctly.
- Moved the test Form handlers into their own folder so they're not cluttering up the Pages folder.
* web: add radius to application wizard
This commit continues the application wizard buildout. In this commit are the following changes:
- Fixed a width-setting bug in the Makefile `make help` feature (i.e "automate that stuff!")
- Added Radius to the list of providers we can offer via the wizard
- Added `launchUrl` and `UI Settings` to features of the application page the wizard can find
- Changed 'SAML Manual Configuration' to just say "SAML Configuration"
- Modified `ak-form-group` to take and honor the `aria-label` property (which in turn makes it
easier to target specific forms with unit testing)
- Reduced the log level for wdio to 'warn'; 'info' was super-spammy and not helpful. It can be put
back with `--logLevel info` from the command line.
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: application wizard refactor & completion
This commit refactors the various components of the Wizard and ApplicationWizard, creating a much
more maintainable and satisfying Wizard experience for both developers (i.e, *me* and *Jens* so
far), and for the customer.
The Wizard base has been refactored into three components:
**AkWizardController**
The `AkWizardController` provides the event listenters for the wizard; it hooks them up, recevies the
events, and forwards them to the wizard. It unwraps the event objects and forwards the relevant
messages contained in the events. It knows of three event categories:
- Navigation requests (move to a different step)
- Update requests (the current step has updated the business content)
- Close requests (close or cancel the wizard).
**ak-wizard-frame**
The `ak-wizard-frame` is the ModalButton interface. It provides the Header, Breadcrumbs (nee`
"navigation block"), Buttons, and a DIV into which the main content is rendered.
**AkWizard**
`AkWizard` is an *incomplete* implementation of the wizard. It's meant to be inherited by a child
class, which will implement the rest. It extends `AKElement`. It provides the basic content needed,
such as steps, currentStep (as an index), an accessor for the step itself, an accessor for the
frame, and the interface to the `AkWizardController`.
**ApplicationWizard**
The `ApplicationWizard` itself has been refactored to accommodate these changes. It inherits from
`AkWizard` and provides the business logic for what to do when a form updates, some custom logic for
preventing moving through the wizard when the forms are incomplete, and a persistence layer for
filling out different providers in the same session. It's simplified a *lot*.
The types specified for `AkWizard` are pretty nifty, I think. I could wish the types being passed
via the custom events were more robust, but [strongly typed custom
events](https://github.com/lit/lit-element/issues/808) turn out to be quite the pain in the, er,
neck. As it is, the `precommit` pass did very good at preventing the worst disasters.
The steps themselves were re-written as objects so that they could take advantage of their `valid`
and `disabled` states and provide more meaningful buttons and labels. I think it's a solid
compromise, and it moved a lot of display logic out of the core `handleUpdate()` business method.
The tests, such as they are, are passing.
* Added comment describing new test.
* web: ensuring copy from `main` is canon
* web: fixes after merge
* web: laying the groundwork for future expansion
This commit is a hodge-podge of updates and changes to the web. Functional changes:
- Makefile: Fixed a bug in the `help` section that prevented the WIDTH from being accurately
calculated if `help` was included rather than in-lined.
- ESLint: Modified the "unused vars" rule so that variables starting with an underline are not
considered by the rule. This allows for elided variables in event handlers. It's not a perfect
solution-- a better one would be to use Typescript's function-specialization typing, but there are
too many places where we elide or ignore some variables in a function's usage that switching over
to specialization would be a huge lift.
- locale: It turns out, lit-locale does its own context management. We don't need to have a context
at all in this space, and that's one less listener we need to attach t othe DOM.
- ModalButton: A small thing, but using `nothing` instead of "html``" allows lit better control over
rendering and reduces the number of actual renders of the page.
- FormGroup: Provided a means to modify the aria-label, rather than stick with the just the word
"Details." Specializing this field will both help users of screen readers in the future, and will
allow test suites to find specific form groups now.
- RadioButton: provide a more consistent interface to the RadioButton. First, we dispatch the
events to the outside world, and we set the value locally so that the current `Form.ts` continues
to behave as expected. We also prevent the "button lost value" event from propagating; this
presents a unified select-like interface to users of the RadioButtonGroup. The current value
semantics are preserved; other clients of the RadioButton do not see a change in behavior.
- EventEmitter: If the custom event detail is *not* an object, do not use the object-like semantics
for forwarding it; just send it as-is.
- Comments: In the course of laying the groundwork for the application wizard, I throw a LOT of
comments into the code, describing APIs, interfaces, class and function signatures, to better
document the behavior inside and as signposts for future work.
* web: permit arrays to be sent in custom events without interpolation.
* actually use assignValue or rather serializeFieldRecursive
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: eslint & prettier fixes, plus small aesthetic differences.
* Restoring this file. Not sure where it disappears to.
* fix label in dark mode
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* SCIM Manuel -> SCIM
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* fix lint errors
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* web: better converter configuration, CSS repair, and forward-domain-proxy
1. Forward Domain Proxy. I wasn't sure if this method was appropriate for the wizard,
but Jens says it is. I've added it.
2. In the process of doing so, I decided that the Provider.converter field was overly
complexified; I tried too hard to reduce the number of functions I needed to define,
but in the process outsourced some of the logic of converting the Wizard's dataset
into a property typed request to the `commit` phase, which was inappropriate. All
of the logic about a provider, aside from its display, should be here with the code
that distinguishes between providers. This commit makes it so.
3. Small CSS fix: the fields inherited from the Proxy provider forms had some unexpected
CSS which was causing a bit of a weird indent. That has been rectified.
* web: running pre-commit after merge.
* web: ensure the applications wizard tests finish after current changes
* prettier has opinions.
* web: application wizard spit & polish
The "ApplicationWizardHint" now correctly uses the localstorage and allows the user to navigate back
and see the message after it's been hidden, so that it will always be available during the test
phase.
The ApplicationList's old "Create Application Form" button has been restored for the purposes of the
test phase.
The ApplicationWizard is now available on both the ApplicationList and ProviderList pages.
Tana and I discussed the microcopy, putting a stronger second-person "You can do..." twist onto the
language, to give the user the sense of empowerment.
The ShowHintController now has both "hide" and "show" operations, to support the hint restoration.
* web: updated storybook stories for the wizard, illustration how "a simple wizard" is configured in source code and tested with storybook.
* web: I hate getting spanked by prettier.
* web: sometimes I wish I had lower standards
Anyway, this was a very stupid bug, because by definition function
definition arguments don't have uses, they're being defined, not
implemented. Fixed, conf fixed to compensate, and consequences
conquered.
* move context from labs to main
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* Revert "move context from labs to main"
This reverts commit 3718ee69048966d26b1c357a7d2653fbb3ab613b.
* web: reify the data loop
I was very unhappy with the "update this dot-path" mechanism I was using earlier; it was hard
for me to read and understand what was happening, and I wrote the darned thing. I decided instead
to go with a hard substitution model; each phase of the wizard is responsible for updating the
*entire* payload, mostly by creating a new payload and substituting the field value associated
with the event.
On the receiver, we have to do that *again* to handle the swapping of providers when the user
chooses one and then another. It looks clunky, and it is, but it's *legible*; a junior dev
could understand what it's doing, and that's the goal.
* Revert "web: reify the data loop"
This reverts commit 09fedcacf02a90a021ce9e18c0eb4bec1ef48302.
* web: revert the 'lit' to 'lit-labs' for task and context.
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-18 19:43:37 +00:00
|
|
|
//
|
|
|
|
// =====
|
|
|
|
// Hooks
|
|
|
|
// =====
|
|
|
|
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
|
|
|
|
// it and to build services around it. You can either apply a single function or an array of
|
|
|
|
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
|
|
|
|
// resolved to continue.
|
|
|
|
/**
|
|
|
|
* Gets executed once before all workers get launched.
|
|
|
|
* @param {object} config wdio configuration object
|
|
|
|
* @param {Array.<Object>} capabilities list of capabilities details
|
|
|
|
*/
|
|
|
|
// onPrepare: function (config, capabilities) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed before a worker process is spawned and can be used to initialise specific service
|
|
|
|
* for that worker as well as modify runtime environments in an async fashion.
|
|
|
|
* @param {string} cid capability id (e.g 0-0)
|
|
|
|
* @param {object} caps object containing capabilities for session that will be spawn in the worker
|
|
|
|
* @param {object} specs specs to be run in the worker process
|
|
|
|
* @param {object} args object that will be merged with the main configuration once worker is initialized
|
|
|
|
* @param {object} execArgv list of string arguments passed to the worker process
|
|
|
|
*/
|
|
|
|
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed just after a worker process has exited.
|
|
|
|
* @param {string} cid capability id (e.g 0-0)
|
|
|
|
* @param {number} exitCode 0 - success, 1 - fail
|
|
|
|
* @param {object} specs specs to be run in the worker process
|
|
|
|
* @param {number} retries number of retries used
|
|
|
|
*/
|
|
|
|
// onWorkerEnd: function (cid, exitCode, specs, retries) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed just before initialising the webdriver session and test framework. It allows you
|
|
|
|
* to manipulate configurations depending on the capability or spec.
|
|
|
|
* @param {object} config wdio configuration object
|
|
|
|
* @param {Array.<Object>} capabilities list of capabilities details
|
|
|
|
* @param {Array.<String>} specs List of spec file paths that are to be run
|
|
|
|
* @param {string} cid worker id (e.g. 0-0)
|
|
|
|
*/
|
|
|
|
// beforeSession: function (config, capabilities, specs, cid) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed before test execution begins. At this point you can access to all global
|
|
|
|
* variables like `browser`. It is the perfect place to define custom commands.
|
|
|
|
* @param {Array.<Object>} capabilities list of capabilities details
|
|
|
|
* @param {Array.<String>} specs List of spec file paths that are to be run
|
|
|
|
* @param {object} browser instance of created browser/device session
|
|
|
|
*/
|
|
|
|
before: function (_capabilities, _specs) {},
|
|
|
|
/**
|
|
|
|
* Runs before a WebdriverIO command gets executed.
|
|
|
|
* @param {string} commandName hook command name
|
|
|
|
* @param {Array} args arguments that command would receive
|
|
|
|
*/
|
|
|
|
// beforeCommand: function (commandName, args) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Hook that gets executed before the suite starts
|
|
|
|
* @param {object} suite suite details
|
|
|
|
*/
|
|
|
|
// beforeSuite: function (suite) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Function to be executed before a test (in Mocha/Jasmine) starts.
|
|
|
|
*/
|
|
|
|
// beforeTest: function (test, context) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
|
|
|
|
* beforeEach in Mocha)
|
|
|
|
*/
|
|
|
|
// beforeHook: function (test, context) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
|
|
|
|
* afterEach in Mocha)
|
|
|
|
*/
|
|
|
|
// afterHook: function (test, context, { error, result, duration, passed, retries }) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Function to be executed after a test (in Mocha/Jasmine only)
|
|
|
|
* @param {object} test test object
|
|
|
|
* @param {object} context scope object the test was executed with
|
|
|
|
* @param {Error} result.error error object in case the test fails, otherwise `undefined`
|
|
|
|
* @param {*} result.result return object of test function
|
|
|
|
* @param {number} result.duration duration of test
|
|
|
|
* @param {boolean} result.passed true if test has passed, otherwise false
|
|
|
|
* @param {object} result.retries information about spec related retries, e.g. `{ attempts: 0, limit: 0 }`
|
|
|
|
*/
|
|
|
|
// afterTest: function(test, context, { error, result, duration, passed, retries }) {
|
|
|
|
// },
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hook that gets executed after the suite has ended
|
|
|
|
* @param {object} suite suite details
|
|
|
|
*/
|
|
|
|
// afterSuite: function (suite) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Runs after a WebdriverIO command gets executed
|
|
|
|
* @param {string} commandName hook command name
|
|
|
|
* @param {Array} args arguments that command would receive
|
|
|
|
* @param {number} result 0 - command success, 1 - command error
|
|
|
|
* @param {object} error error object if any
|
|
|
|
*/
|
|
|
|
// afterCommand: function (commandName, args, result, error) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed after all tests are done. You still have access to all global variables from
|
|
|
|
* the test.
|
|
|
|
* @param {number} result 0 - test pass, 1 - test fail
|
|
|
|
* @param {Array.<Object>} capabilities list of capabilities details
|
|
|
|
* @param {Array.<String>} specs List of spec file paths that ran
|
|
|
|
*/
|
|
|
|
// after: function (result, capabilities, specs) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed right after terminating the webdriver session.
|
|
|
|
* @param {object} config wdio configuration object
|
|
|
|
* @param {Array.<Object>} capabilities list of capabilities details
|
|
|
|
* @param {Array.<String>} specs List of spec file paths that ran
|
|
|
|
*/
|
|
|
|
// afterSession: function (config, capabilities, specs) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed after all workers got shut down and the process is about to exit. An error
|
|
|
|
* thrown in the onComplete hook will result in the test run failing.
|
|
|
|
* @param {object} exitCode 0 - success, 1 - fail
|
|
|
|
* @param {object} config wdio configuration object
|
|
|
|
* @param {Array.<Object>} capabilities list of capabilities details
|
|
|
|
* @param {<Object>} results object containing test results
|
|
|
|
*/
|
|
|
|
// onComplete: function(exitCode, config, capabilities, results) {
|
|
|
|
// },
|
|
|
|
/**
|
|
|
|
* Gets executed when a refresh happens.
|
|
|
|
* @param {string} oldSessionId session ID of the old session
|
|
|
|
* @param {string} newSessionId session ID of the new session
|
|
|
|
*/
|
|
|
|
// onReload: function(oldSessionId, newSessionId) {
|
|
|
|
// }
|
web: add webdriverIO testing layer (#6959)
* web/add webdriverIO testing layer
This commit adds WebdriverIO as an end-to-end solution to unit testing. WebdriverIO can be run both
locally and remotely, supports strong integration with web components, and is generally robust for
use in pipelines. I'll confess to working through a tutorial on how to do this for web components,
and this is just chapter 2 (I think there are 5 or so chapters...).
There's a makefile, with help! If you just run `make` it tells you:
```
Specify a command. The choices are:
help Show this help
node_modules Runs `npm install` to prepare this feature
precommit Run the precommit: spell check all comments, eslint with sonarJS, prettier-write
test-good-login Test that we can log into the server. Requires a running instance of the server.
test-bad-login Test that bad usernames and passwords create appropriate error messages
```
... because Makefiles are documentation, and documentation belongs in Makefiles.
I've chosen to go with a PageObject-oriented low-level DSL; what that means is that for each major
components (a page, a form, a wizard), there's a class that provides human-readable names for
human-interactable and human-viewable objects on the page. The LoginPage object, for example, has
selectors for the username, password, submit button, and the failure alert; accessing those allows
us to test for items as expected., and to write a DSL for "a good login" that's as straightforward
as:
```
await LoginPage.open();
await LoginPage.login("ken@goauthentik.io", "eat10bugs");
await expect(UserLibraryPage.pageHeader).toHaveText("My applications");
```
There was a *lot* of messing around with the LoginPage to get the username and password into the
system. For example, I had to do this with all the `waitForClickable` and `waitForEnable` because
we both keep the buttons inaccessible until the form has something and we "black out" the page (put
a darkening filter over it) while accessing the flow, meaning there was a race condition such that
the test would attempt to interact with the username or password field before it was accessible.
But this works now, which is very nice.
``` JavaScript
get inputUsername() {
return $('>>>input[name="uidField"]');
}
get btnSubmit() {
return $('>>>button[type="submit"]');
}
async username(username: string) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.btnSubmit.waitForEnabled();
await this.btnSubmit.click();
}
```
The bells & whistles of *Prettier*, *Eslint*, and *Codespell* have also been enabled. I do like my
guardrails.
* web/adding tests: added comments and cleaned up some administrative features.
* web/test: changed the name of one test to reflect it's 'good' status
* web: improve testing by adding test admin user via blueprint
* fix blueprints
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* update package name
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add dependabot
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* prettier run
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* add basic CI
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
* remove hooks
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
---------
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Co-authored-by: Jens Langhammer <jens@goauthentik.io>
2023-10-02 15:40:39 +00:00
|
|
|
};
|