Is it possible to tell Cypress to launch Chrome with a certain language (e.g. German) as I have an application which I need to test in multiple languages. I can't see this detailed anywhere in the documentation which suggests it is not possible at present.
I have tried adding the --lang argument when Chrome is launched but this does not seem to have any effect and Chrome still uses English. See the pluginsFile code below.
module.exports = (on, config) => {
on('before:browser:launch', (browser = {}, args) => {
if (browser.name === 'chrome') {
args.push('--lang=de')
return args
}
})
}
I have also tried --lang=de-DE which also did not work.
See full example in https://glebbahmutov.com/blog/cypress-tips-and-tricks/#control-navigatorlanguage but in short
it('shows Klingon greeting', () => {
cy.visit('index.html', {
onBeforeLoad (win) {
// DOES NOT WORK
// Uncaught TypeError: Cannot assign to read only property
// 'language' of object '[object Navigator]'
// win.navigator.language = 'Klingon'
// instead we need to define a property like this
Object.defineProperty(win.navigator, 'language', {
value: 'Klingon'
})
}
})
cy.contains('#greeting', 'nuqneH').should('be.visible')
})
I had a similar problem, when launching cypress the browser would be in my default language (Dutch), while all our tests expect English to be the default. I found a question on a support-forum mentioning the parameter --lang param as well, but it had no effect on my browser's language.
In the end I could work around the problem by changing the LANG environment variable - I am using Linux. In the terminal I typed the following:
export LANG="en_EN.UTF-8"
and then I ran cypress from the same terminal.
You could script this, and for other operating systems such as MacOS and Windows there's probably a similar environment variable.
Besides adding command line options, you can also change browser preferences using Cypress' Browser Launch API (documentation). This allows you to override the Accept-Language header setting like this:
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium' && browser.name !== 'electron') {
launchOptions.preferences.default.intl = { accept_languages: "nl" }
return launchOptions
}
}
Note that the launchOptions.preferences.default object may be empty, so trying to assign to launchOptions.preferences.default.intl.accept_languages directly may fail.
For one of our projects, this was enough to get the site we were testing to appear in the right language. If you need more, there are more language settings you can try altering (see Chrome's source code and look for "intl").
On a side note, it looks like the --lang command line option only works on Windows, according to Chrome's documentation. On Mac, you are required to change your system preferences, and on Linux, you can use the LANGUAGE environment variable.
cy.visit('/', {
onBeforeLoad(win: Cypress.AUTWindow) {
Object.defineProperty(win.navigator, 'language', { value: 'de-DE' });
},
});
Related
In the latest release of vscode (1__49), there is a code snippet on creating a new link provider. https://code.visualstudio.com/updates/v1_49. I can't seem to find a reference on where to apply this code.
window.registerTerminalLinkProvider({
provideTerminalLinks: (context, token) => {
// Detect the first instance of the word "test" if it exists and linkify it
const startIndex = (context.line as string).indexOf('test');
if (startIndex === -1) {
return [];
}
// Return an array of link results, this example only returns a single link
return [
{
startIndex,
length: 'test'.length,
tooltip: 'Show a notification',
// You can return data in this object to access inside handleTerminalLink
data: 'Example data'
}
];
},
handleTerminalLink: (link: any) => {
vscode.window.showInformationMessage(`Link activated (data = ${link.data})`);
}
});
What is the process for getting the editor to utilize this feature?
You will need to create a vscode extension that includes your code.
As it so happens, I have just set up a fresh extension that will use the TerminalLinkProvider. You can take a look at how the sample code integrates into a sample extension on GitHub.
A good place to start with your first extension is the official guide.
After that, just add your code to the activate(...) function of your extension.
You can built your extension as a .vsix file and install it in any vscode instance you use, but if you think that your code might be of value to others, consider publishing it!
I'm trying to find a way to check if an error has been written to the console when running a cypress unit test.
I know how to log something to the console
cy.log('log this to the console');
but not how to check if an error has been written to it.
any suggestions how to read errors from the (browser) console log?
note: probably not the "smart" way to test but sometimes my js libraries which I use would "complain" and write the errors to the browser log. this is to simplify testing.
This does exactly what I needed of catching any error in the console and do an assertion of the logs count. Just add the following in cypress/support/index.js
Cypress.on('window:before:load', (win) => {
cy.spy(win.console, 'error');
cy.spy(win.console, 'warn');
});
afterEach(() => {
cy.window().then((win) => {
expect(win.console.error).to.have.callCount(0);
expect(win.console.warn).to.have.callCount(0);
});
});
There have been some updates since the previous answers.
Because the window is re-created with each cy.visit, Cypress recommends stubbing as a part of the cy.visit command.
cy.visit('/', {
onBeforeLoad(win) {
cy.stub(win.console, 'log').as('consoleLog')
cy.stub(win.console, 'error').as('consoleError')
}
})
//...
cy.get('#consoleLog').should('be.calledWith', 'Hello World!')
cy.get('#consoleError').should('be.calledOnce')
For more details see the official FAQ for stubbing out the console: https://docs.cypress.io/faq/questions/using-cypress-faq.html#How-do-I-spy-on-console-log
And the recipe repository: https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__console
Edit: the following does not directly log to terminal when in headless mode, but it nonetheless fails the test on AUT's console.error and displays the error message indirectly, even in the headless terminal, which may be what you want.
I'm not sure exactly what you mean, but let's go through all the places where an output can be logged in cypress, and how to handle several cases.
First, an overview:
To log into the command log, you use:
// from inside your test
cy.log('foo');
To log into devTools console:
// from inside your test
console.log('bar');
To log into terminal, you need to log from within the Cypress' node process:
// from within e.g. your plugin/index.js file
console.log('baz');
How to log AUT's errors to Terminal, Command Log, and fail the test
(note, AUT here stands for Application under test, meaning your application).
I'm also using ansicolor package to make the error red-colored in the terminal, which is optional.
// plugins/index.js
const ansi = require(`ansicolor`);
module.exports = ( on ) => {
on(`task`, {
error ( message ) {
// write the error in red color
console.error( ansi.red(message) );
// play `beep` sound for extra purchase
process.stdout.write(`\u0007`);
return null;
}
});
};
Note: using internal cy.now() command to work around Cypress' tendency to throw Cypress detected that you returned a promise when it (IMO) shouldn't.
(adapted from https://github.com/cypress-io/cypress/issues/300#issuecomment-438176246)
// support/index.js or your test file
Cypress.on(`window:before:load`, win => {
cy.stub( win.console, `error`, msg => {
// log to Terminal
cy.now(`task`, `error`, msg );
// log to Command Log & fail the test
throw new Error( msg );
});
});
Currently there is no straightforward way to do what you are asking but there have been some good discussions on how best to get this information. I copied one solution here but if you follow the github link you can see other solutions proposed.
This snippet was taken from the github issue found here: https://github.com/cypress-io/cypress/issues/300
Just FYI the one easy solution is just to spy on console functions.
cy.window().then((win) => { cy.spy(win.console, "log") })
That will print a command log every time that function is called, and
you could also then assert what has been logged.
Another option depending on why you want to assert that something went wrong is to print the error out under the tests in headless mode. The VP of engineering created an NPM package that does this for you.
Cypress-failed-log
The most easiest way if you simply want to ensure that no error is in the console (which is the most usecase I assume).
# npm
npm install cypress-fail-on-console-error --save-dev
# yarn
yarn add cypress-fail-on-console-error -D
And then add to your support/index.ts file:
import failOnConsoleError from "cypress-fail-on-console-error"
failOnConsoleError()
Now your cypress tests are failing just in time when a console error is printed.
This is the working solution I currently use to check for console errors.
let windowConsoleError;
Cypress.on('window:before:load', (win) => {
windowConsoleError = cy.spy(win.console, 'error');
})
afterEach(() => {
expect(windowConsoleError).to.not.be.called;
})
Development Environment OS: Windows 7 Enterprise LTS
Browser compatibility minimum requirements: Should support all Edge, Firefox, Chrome browsers, as of 2018.
Current ongoing issue: Unable to run VM on dev workstation; Cannot run Windows 10 VMs to debug Microsoft Edge extensions.
To explain:
An "all-in-one browser extension" refers to a browser extension code that uses the same code with minor differences to work on various WebExtensions / Chrome Extensions supported browsers. At bare minimum, the same codebase should work and run on Edge, Firefox, and Chrome with very minor changes.
Callbacks on the content scripts for Edge/Firefox/Chrome extensions are handled differently.
For unknown reasons, I cannot run VM on my workstation machine. When VM is running, VM client is black. This is a localized issue on my end that I cannot resolve, so I'm forced to find a different solution/alternative.
How are they handled differently on the content scripts:
Edge: browser.runtime.sendMessage uses callbacks, and returns undefined.
Firefox: browser.runtime.sendMessage uses Promises, and returns a Promise.
Chrome: chrome.runtime.sendMessage uses callbacks, and returns undefined.
According to various references:
Firefox / Chrome / MS Edge extensions using chrome.* or browser.*
https://www.smashingmagazine.com/2017/04/browser-extension-edge-chrome-firefox-opera-brave-vivaldi/
On the content scripts, you can declare the following JavaScript snippet at the top in order to create a global variable that can be referenced everywhere else:
//Global "browser" namespace definition.
window.browser = (function() {
return window.msBrowser || window.browser || window.chrome;
})();
Unfortunately, because of the issue I'm experiencing (VM not running), I cannot tell if window.msBrowser is still being used. And this solution is not helpful for me when handling message callbacks when using namespace.runtime.sendMessage.
With all that said, my main question is: How to write a message passing function that can handle callbacks properly?
Currently, I'm using the following code:
function sendGlobalMessage(messageRequest, callback) {
if (chrome && window.openDatabase) {
//This is Chrome browser
chrome.runtime.sendMessage(messageRequest, callback);
}
else if (browser) {
try {
//Edge will error out because of a quirk in Edge IndexedDB implementation.
//See https://gist.github.com/nolanlawson/a841ee23436410f37168
let db = window.indexedDB.open("edge", (Math.pow(2, 30) + 1));
db.onerror = function(e) {
throw new Error("edge is found");
};
db.onsuccess = function(e) {
//This is Firefox browser.
browser.runtime.sendMessage(messageRequest).then(callback);
};
}
catch (e) {
//This is Edge browser
browser.runtime.sendMessage(messageRequest, callback);
}
}
}
I truly felt this is a hacky solution, because the code is based off of browser platform exclusive quirks in order to separate chrome.runtime.sendMessage and browser.runtime.sendMessage API calls, so as to handle callbacks in their respective platforms. I really wanted to change this.
So I'm asking what better ways are there, out there, that is useful to detect the different platforms, and handle message passing callbacks properly at the same time?
Thanks in advance.
I believed I solved it.
EDIT: The FINAL final version (updated and more stable, less message passing):
//Global "browser" namespace definition, defined as "namespace". Can be renamed to anything else.
window.namespace = (function() {
return window.browser || window.chrome;
})();
function sendGlobalResponse(message, callback){
if (window.namespace === window.chrome) {
//Chrome
window.namespace.runtime.sendMessage(message, callback);
}
else if (window.namespace === window.browser) {
//Using instanceof to check for object type, and use the returned evaluation as a truthy value.
let supportPromises = false;
try {
supportPromises = window.namespace.runtime.getPlatformInfo() instanceof Promise;
}
catch(e) { }
if (supportPromises){
//Firefox
window.namespace.runtime.sendMessage(message).then(callback);
}
else {
//Edge
window.namespace.runtime.sendMessage(message, callback);
}
}
}
(Original Post):
The final version (Now obsoleted):
//Global "browser" namespace definition.
window.namespace = (function() {
return window.browser || window.chrome;
})();
function sendGlobalResponse(message, callback){
if (window.namespace === window.chrome) {
//Chrome
window.namespace.runtime.sendMessage(message, callback);
}
else if (window.namespace === window.browser) {
let returnValue = window.namespace.runtime.sendMessage({});
if (typeof returnValue === "undefined"){
//Edge
window.namespace.runtime.sendMessage(message, callback);
}
else {
//Firefox
window.namespace.runtime.sendMessage(message).then(callback);
}
}
}
In the second if statement, by checking to see if the return value of a window.browser.runtime.sendMessage is a Promise or undefined, we can detect if the platform is Firefox or Edge.
I think this is the only solution to handle message passing callbacks/message responses on the content scripts.
I really couldn't think of a better solution than this. So I'll be using this from now on.
But if anyone else knows a better way, a way where you don't need to send out 1 extra dummy message for Firefox and Edge per function call, that would be great!
It sucks that anything inside the content script is not persistent, and even if you store information about what platform the code is being run on, you still have to fetch the information from the background script before filtering out which runtime.sendMessage function to call on, so it doesn't really save much time.
After my program installed I need installer to add program path to Windows system variable PATH. How to make this?
Installer must do this not me.
UPD:
And program path must be removed with uninstallation too.
UPD2:
Now I'm trying to do like this:
function Component()
{
installer.installationFinished.connect(this, Component.prototype.installationFinishedPageIsShown);
installer.uninstallationFinished.connect(this, Component.prototype.uninstallationFinishedPageIsShown);
}
Component.prototype.installationFinishedPageIsShown = function()
{
try {
if (installer.isInstaller() && installer.status == QInstaller.Success) {
installer.executeDetached("set", "PATH=%PATH%;#TargetDir#");
}
} catch(e) {
console.log(e);
}
}
Component.prototype.uninstallationFinishedPageIsShown = function()
{
try {
if (installer.isUninstaller() && installer.status == QInstaller.Success) {
installer.executeDetached("set", "PATH=%PATH:;#TargetDir#=%");
}
} catch(e) {
console.log(e);
}
}
but it doesn't work :(
I just also struggled a lot with the arguments to the executeDetached function (OS X environment).
Because, apparently, characters are escaped when using a string 'inline'.
For me it worked by moving the arguments to a separate javascript variable, like:
var args = "PATH=%PATH:;#TargetDir#=%"
installer.executeDetached("set", args);
or even
var args = ["PATH=%PATH:;#TargetDir#=%"]
installer.executeDetached("set", args);
Did not validate, but hopefully it could point you, or others, in the right direction.
Also, wrapping the executeDetached in a console.log() helped me debugging a lot!
From the desktop, right click the Computer icon.
Choose Properties from the context menu.
Click the Advanced system settings link.
Click Environment Variables. ...
In the Edit System Variable (or New System Variable) window, specify the value of the PATH environment variable.
I want to create a TRAP function where in debug mode it is the "debugger;" call and in release mode it does nothing, preferably not even a call/return.
I have come up with:
// declare it
var trap = function () { debugger; }
// ...
// use it
trap();
And then for release mode it's:
var trap = function () { }
So, question #1 is, is this the best way to do this?
And question #2 is, is there a way to do an "#if DEBUG , #else, #endif" type of pragmas around it, or do we need to change it by hand when we build for production?
I'm not sure how you define "debug" mode exactly, but if debug mode is more than just what scripts are compiled then I'd generally just conditionalize the function as you've done (I've worked on a number of JavaScript apps for example where we have had a "debug" mode even when the minified scripts were released to help with customer issues in production ... it was activated either through a cookie or a query string):
// this would be set for example to true when
// in debug mode
export var isDebugModeActive: boolean = false;
export var trap = () => {
debugger;
};
if (isDebugModeActive) {
// since you may not want to conditionalize
// through a #PRAGMA like system every call to
// trap, this just no-ops the function
trap = () => {};
}
// or
trap = () => {
// checking a boolean is going to be
// really fast ... :)
if (isDebugModeActive) {
debugger;
}
}
Having a "debug" mode works well when you want to infrequently, but sometimes output additional information to the browser console log for example.
I found this page by search. In case others do too. This is what I was looking for.
type Noop = () => void;
// tslint:disable-next-line:no-empty
const noop: Noop = () => {};
Example use:
const onPress = (buttons && buttons[0].onPress) || noop;
The simplest way of accomplishing this would be to have two versions of your file - a trap.debug.ts and a trap.prod.ts file.
trap.prod.ts would include the function definition, and then do nothing.
You should be able to use MVC Bundles or SquishIt on the server side with a #if DEBUG attribute to include the debug or prod version of your script.
Hope this helps.