We're using Protractor and Appium to write tests for a hybrid native application.
I'm having intermittent trouble with the 'autowebview' appium capability. I suspect it is a race condition between a slow emulator changing contexts and protractor sending commands only supported by a webview context.
What I'd like to do is manually set the context to WEBVIEW, in Protractor's onPrepare callback, thanking that this will cut off the race condition described above.
There are code snippets out there like the following that switch the context:
onPrepare: function () {
var wd = require('wd'),
wdBridge = require('wd-bridge')(protractor, wd),
_ = require('underscore');
wdBridge.initFromProtractor(exports.config);
return wdBrowser.contexts().then(function (ctxs) {
var webCtx = _(ctxs).find(function (ctx) {
return ctx.match(/WEBVIEW/);
});
return wdBrowser.context(webCtx)
});
}
However, in my case 'wdBrowser.contexts' is undefined. 'wdBrowser' is a global created by wdBridge; it's defined, but that function is undefined on it, and there's no reference to 'contexts' in the wd-bridge repo. I see that it is a defined command in wd, but I can't find a reference in the WebDriverJS API docs.
Interestingly, it's clearly defined by WebdriverIO, in it's appium section. I'm not quite ready to give up on protractor and wd yet though.
Thanks for any help.
Related
I need to access a public object available on an iframe scope but the code that I'm running on the ClientFunction gets executed on the parent, I managed to get it working using the --disable-web-security flag and accesing the frame like this window.frames['0'].store. (Not happy with that hack TBH)
But now looks like TestCafe updated to some newer version of Chromium and there's a message telling me that the flag is not allowed anymore.
Is there any way to run client code targetting a specific iframe without needing that nasty flag?
To run ClientFunction on an iframe, you need to switch to it beforehand.
const fn = ClientFunction(() => true);
test('test', async t => {
await t.switchToIframe('#iframe');
await fn();
});
So I am coding a VueJS and ElectronJS template which can be found here: https://github.com/dev-aethex/electronjstemplate
My code works something like this,
Inside of my Vue component I access a global pre constructed class called MainProcessInterface and when it's constructed it first checks if vue is compiled for running in a development server. If it's in a dev server it will connect to the dev socket which electrons main process will host if electron is in dev mode and not compiled. This method seems to be working great, I had to use a socket because vue dev server is being loaded into electron via loadURL and so vue has no clue what ipcRenderer is. Inside the main process interface, if vue is compiled it will instead use the ipcRenderer.send() method. This is were the problem was born.
As soon as Vue runs thought the TS code, it sees ipcRenderer.send and freaks out while printing an error to the electron window console saying fs.existsSync does not exist or is defined.
I can't find a way around this. I though maybe i'll split MainProcessInterface into 2 peices, one for ipc and the other for websockets. Although it isn't a very good way, so before implementing it, I would like to know if there is a better more proper way of doing such.
I had a similar issue with React. Are you importing the ipcRenderer object somewhere in your build process? You might want to make sure it references the correct variable. I tried to drop this in as a comment but it wouldn't fit:
//index.html (index.ejs) for me... This is in the main HTML entry point
var IPC = null;
try {
IPC = require('electron').ipcRenderer;
console.log('IPC IS: ' + IPC)
} catch (err) {
console.log('CRICITCAL ERROR: IPC NOT ENABLED')
console.log(err)
IPC = null;
}
Then I initialize off that context in React with a startup here:
setTimeout(()=>{
console.log('----------------HACK FIRED POST REHYDRATE')
window.REDUX_STORE.dispatch(
(dispatch, getState) => {
const _state = getState()
if(window.IPC) {
if(_state.osc && _state.osc.on) {
dispatch( reconnectToEos() )
} else {
dispatch( updateStatus('[OSC Startup: DISCONNECTED]', ))
}
console.log('\t------------ELECTRON')
} else {
//Shut off OSC
dispatch( updateOscKey('on', false) )
dispatch( updateStatus('[WebApp, OSC disabled]', ))
console.log('\t------------WEB')
}
}
)
}, 1000)
Basically I'm using a global variable (window.IPC) to initialize my app so I don't import a bad variable in my build process. I have a fair number of Electron APIs where this alleviates the issues with building via Webpack.
I hope this helps!
I have been working in React Native using Expo CLI and recently started to face issue with my Unit tests that got failed because of one common reason. Stack trace is below
Cannot read property 'toString' of undefined
at Converter.toBase64 (node_modules/convert-source-map/index.js:61:46)
at Converter.toComment (node_modules/convert-source-map/index.js:65:21)
at generateCode (node_modules/#babel/core/lib/transformation/file/generate.js:78:76)
at run (node_modules/#babel/core/lib/transformation/index.js:55:33)
at run.next (<anonymous>)
at transform (node_modules/#babel/core/lib/transform.js:27:41)
at transform.next (<anonymous>)
at evaluateSync (node_modules/gensync/index.js:244:28)
at sync (node_modules/gensync/index.js:84:14)
My node version is node:12.18.4.I wonder what caused these errors since everything was working perfectly. On my local system they are working fine, occasionally but CI process tends to fail them randomly which hinders the overall code coverage figures.
Unit test I am trying to run is very simple as written below
it('Renders Strings as expected', () => {
expect(received).toStrictEqual(expected)
})
For those of you who are still wandering around to find answer to above question.
Issue was in the library itself convert-source-map which needed to handle this exception.
I forked the actual repository and handled that exception in line 64 toBase64 method. Now the method looks like something
Converter.prototype.toBase64 = function () {
var json = this.toJSON();
return (SafeBuffer.Buffer.from(json, 'utf8') || "").toString('base64');
};
Now everything is working fine.
Original method was something like this
Converter.prototype.toBase64 = function () {
var json = this.toJSON();
return SafeBuffer.Buffer.from(json, 'utf8').toString('base64');
};
I'm using Quasar framework and just after I've added quasar-dotenv package I realized that e2e tests not working.
Uncaught TypeError: fs.readFileSync is not a function
This error originated from your test code, not from Cypress.
When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure.
Check your console for the stack trace or click this message to see where it originated from.
at Object.config (http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:141291:34)
at Object.746.eslint (http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:150393:36)
at o (http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:1:265)
at http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:1:316
at Object.747.../../../../quasar.conf.js (http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:150535:35)
at o (http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:1:265)
at r (http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:1:431)
at http://localhost:8080/__cypress/tests?p=test/cypress/integration/home/init.spec.js-312:1:460
I've tried to set up the Cypress environment by adding the test/cypress/plugins/cypress.env.json file with some data, as well as changing the test/cypress/plugins/index.js file in the same folder, by following this documentation as it was suggested here:
const env = require('quasar-dotenv').config()
module.exports = (on, config) => {
// config.env.API_URL = 'http://example.com' // not working
config.env = env
// Chrome:: Hack for shaking AUT. Cypress Issue: https://github.com/cypress-io/cypress/issues/1620
on('before:browser:launch', (browser = {}, args) => {
if (browser.name === 'chrome') {
args.push('--disable-blink-features=RootLayerScrolling');
return args;
}
return true;
});
return config
};
MacOS Catalina
Cypress v3.5.0
Chrome v77
I don't know the answer because I am unfamiliar with quasar-dotenv. Have you tried the official quasar dotenv app extension? https://github.com/quasarframework/app-extension-dotenv or, alternatively, another official app extension, but less opinionated than dotenv wrappers: https://github.com/quasarframework/app-extension-qenv
I'm testing a couple of components that reach outside of their DOM structure when mounting and unmounting to provide specific interaction capability that wouldn't be possible otherwise.
I'm using Jest and the default JSDOM initialization to create a browser-like environment within node. I couldn't find anything in the documentation to suggest that Jest reset JSDOM after every test execution, and there's no explicit documentation on how to do that manually if that is not the case.
My question is, does Jest reset the JSDOM instance after every test, suite or does it keep a single instance of JSDOM across all test runs? If so, how can I control it?
To correct the (misleading) accepted answer and explicitly underline that very important bit of information from one of the previous comments:
No. Jest does not clean the JSDOM document after each test run! It only clears the DOM after all tests inside an entire file are completed.
That means that you have to manually cleanup your resources created during a test, after every single test run. Otherwise it will cause shared state, which results in very subtle errors that can be incredibly hard to track.
The following is a very simple, yet effective method to cleanup the JSDOM after each single test inside a jest test suite:
describe('my test suite', () => {
afterEach(() => {
document.getElementsByTagName('html')[0].innerHTML = '';
});
// your tests here ...
});
Rico Pfaus is right, though I found that resetting the innerHTML the way he suggests was too destructive and caused tests to fail. Instead, I found selecting a specific element (by class or id) I want to remove from the document more effective.
describe('my test suite', () => {
afterEach(() => {
document.querySelector(SOME CLASS OR ID).innerHTML = ''
})
})
This is still an issue for many people — and it's the top answer in Google — so I wanted to provide some context from the future ;)
does it keep a single instance of JSDOM across all test runs
Yes, the jsdom instance remains the same across all test runs within the same file
If so, how can I control it?
Long story short: you'll need to manage DOM cleanup yourself.
There is a helpful Github issue on facebook/jest that provides more context and solutions. Here's a summary:
if you want a new jsdom instance then separate your tests into separate files. This is not ideal for obvious reasons...
you can set .innerHTML = '' on the HTML element as mentioned in the accepted answer. That will resolve most issues but the window object will remain the same. Window properties (like event listeners) can persist in subsequent tests and cause unexpected errors.
cleanup the jsdom instance between tests. The jsdom cleanup function doesn't do anything magic — it's basically resetting global properties. Here's an example directly from the Github issue:
const sideEffects = {
document: {
addEventListener: {
fn: document.addEventListener,
refs: [],
},
keys: Object.keys(document),
},
window: {
addEventListener: {
fn: window.addEventListener,
refs: [],
},
keys: Object.keys(window),
},
};
// Lifecycle Hooks
// -----------------------------------------------------------------------------
beforeAll(async () => {
// Spy addEventListener
['document', 'window'].forEach(obj => {
const fn = sideEffects[obj].addEventListener.fn;
const refs = sideEffects[obj].addEventListener.refs;
function addEventListenerSpy(type, listener, options) {
// Store listener reference so it can be removed during reset
refs.push({ type, listener, options });
// Call original window.addEventListener
fn(type, listener, options);
}
// Add to default key array to prevent removal during reset
sideEffects[obj].keys.push('addEventListener');
// Replace addEventListener with mock
global[obj].addEventListener = addEventListenerSpy;
});
});
// Reset JSDOM. This attempts to remove side effects from tests, however it does
// not reset all changes made to globals like the window and document
// objects. Tests requiring a full JSDOM reset should be stored in separate
// files, which is only way to do a complete JSDOM reset with Jest.
beforeEach(async () => {
const rootElm = document.documentElement;
// Remove attributes on root element
[...rootElm.attributes].forEach(attr => rootElm.removeAttribute(attr.name));
// Remove elements (faster than setting innerHTML)
while (rootElm.firstChild) {
rootElm.removeChild(rootElm.firstChild);
}
// Remove global listeners and keys
['document', 'window'].forEach(obj => {
const refs = sideEffects[obj].addEventListener.refs;
// Listeners
while (refs.length) {
const { type, listener, options } = refs.pop();
global[obj].removeEventListener(type, listener, options);
}
// Keys
Object.keys(global[obj])
.filter(key => !sideEffects[obj].keys.includes(key))
.forEach(key => {
delete global[obj][key];
});
});
// Restore base elements
rootElm.innerHTML = '<head></head><body></body>';
});
For those interested, this is the soft-reset I'm using in "jest.setup-tests.js" which does the following:
Removes event listeners added to document and window during tests
Removes keys added to document and window object during tests
Remove attributes on <html> element
Removes all DOM elements
Resets document.documentElement HTML to <head></head><body></body>
— #jhildenbiddle