Improve DX for enigneers using TypeScript (or type checks)
See original GitHub issueWhat are you trying to achieve?
I’m trying to configure our code base in a way that would reliably warn us about type issues. In particular I’d like to see correct type errors when I inject more than one page object into my Scenario.
This problem makes the CodeceptJS typings not usable in this specific case.
What do you get instead?
Instead, whenever I have more than one page object in the Scenario callback, I immediately get a lot of errors.
Details
Here is a dummy example (pseud-code):
Scenario('dum-dum', (I, pageA, pageC, pageB) => {
pageA.doA();
pageB.doB();
pageC.doC();
});
Note that I have purposefully ordered arguments this way (pageC before pageB).
The following code will work fine in runtime but it will fail at “type check” time.
Note that the following is true even if you don’t use TypeScript but simply enable typechecking with the following jsconfig.json
{
"compilerOptions": {
"checkJs": true
}
}
There reason this code fails comes from the way CodeceptJS injects page objects and how that affects ICodeceptCallback
type (steps.d.ts).
CodeceptJS uses fn-args to inject page objects. It reads function’s arguments and it expects exact match between argument name and page object name declared in the config file.
The problem with this approach is that the generated ICodeceptCallback
expects arguments to be passed in a particular order. In context of this example it could look similar to:
type ICodeceptCallback = (i?: CodeceptJS.I, current?:any, pageA: PageA, pageB: PageB, pageC: PageC, ...args: any) => void;
See that it expects the 3rd argument to be of type PageA
. However, in runtime (and plain, unchecked javascript) it’s totally fine to actually put pageC there instead.
- CodeceptJS version: 2.3.2
Proposed solution
All those problems would disappear if ICodeceptCallback
expected just two arguments:
I
- and
custom
Where custom
would be an object containing all user-defined page objects, fragments, etc.
This way the order of parameters would no longer matter and type checking would be happy 😃
If done this way this example scenario would look like so (I’m using destructuring):
Scenario('dum-dum', (I, { pageA, pageC, pageB }) => {
});
and generated ICodeceptCallback
would become
type ICodeceptCallback = (i?: CodeceptJS.I, custom?: { pageA?: PageA, pageB?: PageB, pageC?: PageC }) => void;
Workaround
Possible workaround for people with the same problem is to use inject
function at the top of a testing suite.
Instead of doing
Scenario('x', (I, page) => {})
do
const { page } = inject();
Scenario('x', (I) => {})
Although I don’t know whether that will be the same with regards to page object’s state.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:7 (5 by maintainers)
Top GitHub Comments
Great. Thanks for looking into it @chris-miaskowski. BTW I didn’t know about the refactoring feature in VSCode when moving/renaming file with module. It really works!
I’ve just upgraded to CodeceptJS 2.3.6 and run
npx codeceptjs def
.Now I don’t need to use
const loginPage = require("./pages/Login");
to get correct IntelliSense (https://github.com/Codeception/CodeceptJS/issues/1972#issuecomment-560108412), because the page object definition is already imported insteps.d.ts
, e.g.:So now when I’m clicking on a method of loginPage, it navigates to source of the method as expected.
Of course this issue #1972 is blocking autocomplete/intellisense if passed page objects don’t match order in
steps.d.ts
. Well, a workaround exists - inject objects globally as described in https://github.com/Codeception/CodeceptJS/issues/1972#issue-508517225