JavaScript & Cypress for Oracle devs!


Intro
Some people hate JavaScript, but I love it! Some people hate the way they inherit non-sensical looking JS they can't make head nor tail of: for me, that's part of the challenge of being a dev. PL/SQL can be great too, but it's also a bit staid, wouldn't you say? A bit set in its ways. Brilliant at what it does, of course, but all a bit same-y.
Geek alert!
I've read the Rhino^ book three (3!) times.
4th edition, back in the early 2000s when it was more of a fledgling language and everyone spent half their time on browser compatibility issues – and when IE, MS's Internet Explorer, was still very much the main web browser used worldwide. Thankfully no more!
5th Ed. in the late 2000s (say - it's a while back). IE still forefront, but the better, sleeker, easier for developers browsers starting to catch up in user take-up.
7th Ed. pub. 2020, read 2023. Get it. Go on, get it. Go support your local bookshop, get it, and read it. Yes! If you have any passion for APEX and/or JS, I cannot recommend it enough. (PS. Definitely no need any more to support IE - PHEW!)
^Note: it's real title is JavaScript: The Definitive Guide by David Flanagan, publisher, O'Reilly. It has a Rhino on the front.
What's JavaScript ever done for me?
Well, you wouldn't have APEX without it, for starters! Now, the old age is true here: Just because you can, doesn't mean you should. I am not saying you should embrace that 15 year old heavily JavaScript-influenced APEX app that just fell into your remit, but see it as a chance to refactor, improve, whilst embedding your newly found love of the language.
Top tips for modern JS!
Named parameter invocation
Traditional, un-documenting
In this style, parameter sequence is strict, so you can't skip one and must provide undefined to mimic skipping it entirely. Parameters are named internally to the function, but when you call the function, you just have to provide a mysterious sequence of runtime arguments. Do not write any new functions in this style!
function myFunc(p1, p2, p3){ ... }
const rslt = myFunc(x, y, z);Modern, self-documenting
In this style, as with any object, it is arbitrary what order you define object properties. Crucially however, skipping a property/parameter entirely means it still receives its default value at runtime. If there is any doubt over what the parameters do, comment them! Whilst a non-defaulted parameter can be skipped, it is heavily implied that it is mandatory, and should be assumed to be so.
function myFunc({
p1, // This mandatory parameter does this
p2="I'm defaulted", // this one does that
p3='So am I', // and this one does the other
}){ ... }
const rslt = myFunc({
p1:x, // (implied) mandatory as has no default
p3:y, // override default
// allow p2 to default
});Blended approach
This style is useful when you will almost always call the function with one or two very obvious parameters, but there are occasions you want to alter the normal behaviour slightly. And it is very uhlikely the number of mandatory parameters will ever change.
function loggingFunc(logMsg, opts={
opt1:'doItTheNormalWay',
opt2:'reallyNicheOption', // comma VALID here - alas, not in JSON!!
}){ ... }
const rslt = loggingFunc('log message', opts:{
opt1:'doItTheOtherWay',
opt2:'andInvokeThisRarelyUsedFeature',
});Semi-colons & Cypress - not always optional!
Beware treating the semi-colon as optional! Whilst modern browsers are very good at reading your code as you meant it, sometimes – guess what – they don't!
I'm just starting out in my Cypress journey and have seen a lot of Cypress code that includes implied semi-colons at line-breaks, so I thought, ok, yeah, let's go with it, if that's the trend. Well, beware! I spent an hour or so with what seemed to me perfectly valid implied semi-colons in my Cypress/JavaScript code, but – guess what? – yep, the browser had not implied one where it seemed to me it would.
(My working environment also binds me to Cypress in headless mode, so I can't even log() what I'm doing, as these only appear in the GUI log, not in any headless output. Strange, but true.)
Treat your tests as code!
If you're writing tests, then these are just as important as your business logic; otherwise, the future you (whether actually you or someone else) will treat them with the same contempt that you treated that heavily-JavaScript-laden, non-documented, 15-year old APEX app: they'll throw them away, and start again. So what was the point in that then?
Modularize your test code
Following on from the prior section, don't duplicate logic! Your tests are just as important as your code. Just knowing enough JavaScript to get by is not the answer. If you find yourself duplicating logic, spend some time researching & refactoring. Here's a free example to modularize get|find logic for an element, irrespective of whether it is in an <iframe> or not – many APEX modals are rendered in an <iframe> so you'll need it frequently:
/* commands.js */
import cypress-iframe; // dependency needs installing
Cypress.Commands.add('getAgnostic', ({
dataCyItem = '', // finds a `[data-cy="xyz"]` attribute
customSelector = '', // alt selector if data-cy unavailable
tag = '', // help narrow down no. of elements to look through
iframe = false, // true if element inside <iframe>, eg. modal
ifrmSelector = undefined, // req if >1 iframes, modal on modal
}) => {
let selector = customSelector
|| `${tag}[data-cy="${dataCyItem}"]`;
(iframe
? cy.iframe(ifrmSelector).find(selector)
: cy.get(selector)
)
.should('exist'); // BEWARE 'be.visible' in APEX!
// .should('be.visible') // WILL NOT APPLY to many form elements
// return value still good for chaining
});
/* yourSpecFile.cy.js */
cy.getAgnostic({
dataCyItem:'your-apex-item-data-cy-custom-attrib',
tag:'button', // eg.
iframe:true, // in a modal popover, for example
ifrmSelector:'[title="Modal popup title"]', // only method found thus far
})
.click() // Now there is no need to repeat logic for iframe vs notlet, const, var
Old school JavaScript allowed variables without declaration at all, and they were then scoped globally, wherever defined! If you are supporting old code, you may come across some sloppy code that uses these.
var
var is old school, don't use! It can be the cause of weird and wonderful, difficult to locate bugs, with one bit of code supplanting a previously declared var.
It declares a variable local to that scope, overwriting a previously-declared var with the same name. The variable is accessible anywhere within that scope, even if it is only formally declared with var part-way through that scope.
Declared in global scope, a var is visible everywhere (of course), even in modules.
let
let replaces var. Within scope, a re-declaration of a variable with let will cause a syntax error. These more rigorous declarations can only be used once declared.
const
const of course declares a constant – but don't be fooled! A const doesn't somehow magically lock down a Class instance, for example. You can still invoke methods, and affect its internal state. What you can't do is change that const from being anything other than the thing it started off as, so its original reference or primitive value.
OO JavaScript
Ok, it'll never be a true OO language, but it supports constructs that will be immediately familiar to a C# or Java dev.
Private variables
#priv in a Class declares a private variable (this is a relatively new feature, and was only proposed when the 2020 Rhino book was published).
get, set
A common pattern is to have getters/setters that define like-named accessors stripped of the leading '#'.
But that doesn't mean you can't have a getter that just defines an accessor, maybe something to inspect internal state and reveal a status.
(You can also have a setter without a getter, but I'll leave it for you to think of a weird and wonderful reason why you might want that.)
export default class Person
{
static species = 'Homo Sapiens';
// private, internal variable, with get/set
#mood = 5;
get mood(){ return this.#mood }
set mood(newMood){ this.#mood = newMood }
// property getter (supersedes instance property of same name!)
get howAmIDoing(){
return (
this.#mood > 6
? 'Happy' :
this.#mood < 4
? 'Sad'
: 'So-so'
)
}
// instance method
getId(){ throw 'I am NOT a number!' }
// private variables only accessible via instance methods
#name;
#dob;
#address;
constructor({
name,
dob,
address,
}){
#name = name;
#dob = dob;
#address = address;
})
}
const someone = new Person({ // internal state not constant
name:'The valet',
dob:'17/11/1922',
address:'Downton Abbey',
});
let theirMood = someone.mood;
someone.mood = 9; // sets private variable #mood to 9 via `set`ter function
console.log(someone.howAmIDoing); // 'Happy': as a `get`ter no `()` required
console.log(Person.species); // 'Homo Sapiens'
const theirNumber = someone.getId(); // valid, but will obviously throw an error
someone.#mood = 9; // INVALID
If you liked this blog, please pass on to your colleagues!



