I'm using cypress to test my VueJS application. The one thing I'm having trouble with is mocking an image to be displayed on the page. For my use case, I'm simply loading a user profile with the following code:
describe('Test Login', () => {
it('Can Login', () => {
cy.server();
cy.route({
method: 'GET',
url: '/api/account/',
response: 'fx:profile.json',
});
cy.route('**/media/demo1.png', 'fx:demo1.png');
});
});
fixtures/profile.json
{
"avatar": "http://localhost:8080/media/demo1.png",
"username": "cypress",
"email": "email#cypress.io",
"pk": 1,
"is_staff": true,
"is_superuser": true,
"is_active": true
}
The profile fixture data is loading correctly in the test. In my fixtures folder, I also have a demo1.png file. I am expecting this image to be loaded and displayed on the page during my test, but it is being displayed as a broken image.
In the network tab, it shows demo1.png as a broken image with a 200 response code and type of text/html.
The cypress documentation mostly discusses images in the context of uploading images, but I haven't been able to find an example of how I can mock an image that is loaded through a <img> tag. Is there an easier way of doing this?
I am not sure if this answer can help you. But at least it is a workaround for this problem ;-)
Say we have a HTML like this:
<html>
<body>
<button id="button">load</button>
<div id="profile">
</div>
<script>
function httpGetAsync(theUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(JSON.parse(xmlHttp.responseText));
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
document.getElementById("button").addEventListener("click", () => {
httpGetAsync("/api/account/", (result) => {
var div = document.querySelector("#profile");
var img = document.createElement("img");
img.src = result.avatar;
div.appendChild(img)
})
})
</script>
</body>
</html>
source: HTTP GET request in JavaScript?
And you want to load the profile after the click was done. Then you can use MutationObserver to replace the img.src.
First, write the MutationObserver:
var observeDOM = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ){
if( !obj || !obj.nodeType === 1 ) return; // validation
if( MutationObserver ){
// define a new observer
var obs = new MutationObserver(function(mutations, observer){
callback(mutations);
})
// have the observer observe foo for changes in children
obs.observe( obj, { childList:true, subtree:true });
}
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
(heavily copy & pasted from Detect changes in the DOM)
Now you are able to do this:
describe('Test Login', () => {
it('Can Login', () => {
var win = null;
cy.server();
cy.route({
method: 'GET',
url: '/api/account/',
response: 'fx:profile.json'
});
cy.visit("index.html").then(w => {
cy.get("#profile").then(pro => {
var e = pro[0];
observeDOM(e, (m) => {
// add a red dot image
m[0].addedNodes[0].src = "data:image/png;base64,"+
"iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP"+
"C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA"+
"AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J"+
"REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq"+
"ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0"+
"vr4MkhoXe0rZigAAAABJRU5ErkJggg=="
})
})
cy.get("button").click()
})
});
});
(yeah at least some lines of code are written on my own ;-P)
You can read the image from the img.src attribute from the fixtures folder. For the sake of simplicity I have used a static base64 string here.
And the result:
We are not using this kind of stuff in our aurelia app but I tried similar things in a private project some time ago.
Related
I'm trying to test an a page with an external component which contains a following iframe:
<iframe id="iframe1" src="about:blank" ... >
This iframe has an empty body initially but is populated with content after some actions.
When trying to run the following piece of code:
.expect(myIFrameSelector().visible).ok()
.switchToIframe(myIFrameSelector())
.expect(firstRowSelector()).eql("Hello")
I receive the following error on line 3:
The content of the iframe in which the test is currently operating did not load.
I tried waiting for the contents to appear with wait() and I checked it with debug() too.
Any ideas what could be the problem?
I assume that this is because the content was probably populated using JS, so can I somehow tell testcafe that the content is actually ready?
You can try to wait and check if the document in the iframe is loaded using ClientFunction.
For example:
import { Selector, ClientFunction } from 'testcafe';
const waitForIframeLoad = ClientFunction((iframeSelector) => new Promise((resolve, reject) => {
var i = 0;
var intervalId = null;
intervalId = window.setInterval(() => {
var iframeElement = document.querySelector(iframeSelector);
if (iframeElement
&& iframeElement.contentWindow
&& iframeElement.contentWindow.location.href !== 'about:blank'
&& iframeElement.contentDocument) {
window.clearInterval(intervalId);
resolve();
}
if (i > 60) {
window.clearInterval(intervalId);
reject(new Error('Iframe content loading timeout'))
}
i++;
}, 1000);
}));
fixture`fixture`
.page`http://example.com`;
test('test', async t => {
const iframeSelector = '#simulatorFrame';
await waitForIframeLoad(iframeSelector);
await t
.switchToIframe(iframeSelector)
.click(Selector('button'));
});
So I had asked a question previously, and got a little bit of help as far as logging the results however my results are not making sense.
So I have a input
<input type="file" name="import_file" v-on:change="selectedFile($event)">
The v-on:change binds the selected file to my data object this.file
selectedFile(event) {
this.file = event.target.files[0]
},
and then I submit the file with this method
uploadTodos() {
let formData = new FormData();
formData.append('file', this.file);
for(var pair of formData.entries()) {
console.log(pair[0]+ ', '+ pair[1]);
}
this.$store.dispatch('uploadTodos', formData);
}
However when I submit it seems there is no data attached to formData because my logged result is this
file, [object File]
shouldn't I have my actual data appended to the formData object??
I have referenced other articles on how to post but I am not getting the desired results.
article 1
article2
uploadTodos(context, file) {
console.log(file)
axios.post('/import', file,{ headers: {
'Content-Type': 'multipart/form-data'
}})
.then(response => {
console.log(response.data)
context.commit('importTodos', response.data)
})
.catch(error => {
console.log(error.response.data)
})
}
when I console.log(file) the formData object is empty
Backend Question
So my issue with Laravel on the backend is with the maatwebsite package. From what I have seen is the 3.0 version does not yet support imports. And the only work around suggested is to install version 2.0? Is this still the only workaround? Here is the controller method
public function importExcel(Request $request)
{
if (empty($request->file('file')->getRealPath())) {
return back()->with('success','No file selected');
}
else {
$path = $request->file('file')->getRealPath();
$inserts = [];
Excel::load($path,function($reader) use (&$inserts)
{
foreach ($reader->toArray() as $rows){
foreach($rows as $row){
$inserts[] = ['user_id' => $row['user_id'], 'todo' => $row['todo']];
};
}
});
if (!empty($inserts)) {
DB::table('todos')->insert($inserts);
return back()->with('success','Inserted Record successfully');
}
return back();
}
}
The line not suppported by version 3.0 is this
Excel::load($path,function($reader) use (&$inserts)
I have reproduced your code and it seems to be working fine
when I console.log(file) the formData object is empty
Yeah the output should be an empty object when you console, that's the way javascript works.
after casting the output to an array i get the output in the image below:
const store = new Vuex.Store({
actions: {
uploadTodos(context, file) {
console.log([...file])
axios.post('/import', file,{ headers: {
'Content-Type': 'multipart/form-data'
}})
.then(response => {
console.log(response.data)
context.commit('importTodos', response.data)
})
.catch(error => {
console.log(error.response.data)
})
}
}
})
const app = new Vue({
store,
data: {
file: null
},
methods: {
selectedFile(event) {
console.log(event);
this.file = event.target.files[0]
},
uploadTodos() {
let formData = new FormData();
formData.append('file', this.file);
for(var pair of formData.entries()) {
console.log(pair[0]+ ', '+ pair[1]);
}
this.$store.dispatch('uploadTodos', formData);
}
},
el: '#app'
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
<input type="file" name="import_file" #change="selectedFile($event)">
<button #click="uploadTodos">
Submit
</button>
</div>
This post answers the second part of the question. At first from what I read maatwebsite/excel version 3.0 does not support import. However I am using version 3.1.0 which does support imports. However the method for importing still does not suppport Excel::load(). You should instead use Excel::import() and follow the given rules for passing in parameters. Which of course can be modified to suit your needs. But anyways here is a simple example of how I am using it for anyone interested.
First create import file for whatever model it is. For me it is Todos.
<?php
namespace App\Imports;
use App\Todo;
use Maatwebsite\Excel\Concerns\ToModel;
class TodoImport implements ToModel
{
/**
* #param array $row
*
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function model(array $row)
{
return new Todo([
'user_id' => $row[0],
'todo' => $row[1],
]);
}
}
next you have your controller handling the file, and passing it to the todosimport file
public function importExcel(Request $request)
{
if (empty($request->file('file')->getRealPath())) {
return back()->with('success','No file selected');
}
else {
Excel::import(new TodoImport, $request->file('file'));
return response('Import Succesful, Please Refresh Page');
}
}
notice the Excel::import(). I pass in the new Todo model and the file received.
of course for me since I am doing it by ajax I use this route to ping the method
Route::post('/import', 'TodosController#importExcel');
consider the following snippet.how can i access the value of the page url outside the page context?globally accessing the value was not working either.callbacks wasn't clear to me in approach.
page.onUrlChanged = function(targetUrl) {
console.log('New URL: ' + targetUrl);
};
page.onConsoleMessage = function (msg) {
console.log(msg);
};
var abc=page.open(url,function(status){
page.evaluate(function(){
//some code;
})
return page.url;
});
console.log(abc);
the code always gives undefined page url.
PhantomJS documents are very much recommended: http://phantomjs.org/api/webpage/method/evaluate.html
page.open(url,function(status){
var current_url = page.evaluate(function(){
return document.location.href;
})
console.log(current_url);
});
I've followed examples for injecting jQuery from the getting started page and that works just fine. I have a local copy of jQuery in the same directory, and do something like...
if(page.injectJs('jquery.min.js')) {
page.evaluate(function(){
//Use jQuery or $
}
}
When I try to inject my own script(s), none of the functions are available to me. Say I have a script called myScript.js that just has
function doSomething() {
// doing something...
}
I cannot then use doSomething like...
if(page.injectJs('myScript.js')) {
console.log('myScript injected... I think');
page.evaluate(function() {
doSomething();
});
} else {
console.log('Failed to inject myScript');
}
I've tried
window.doSomething = function() {};
and
document.doSomething = function() {};
as well with no luck, as well as trying to call them with window.doSomething() or document.doSomething() in the subsequent page.evaluate().
The following works for me, maybe some other part of your app logic is wrong:
inject.coffee
page = require('webpage').create()
page.onConsoleMessage = (msg) -> console.log msg
page.open "http://www.phantomjs.org", (status) ->
if status is "success"
page.includeJs "http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", ->
if page.injectJs "do.js"
page.evaluate ->
title = echoAndReturnTitle('hello')
console.log title
phantom.exit()
do.coffee:
window.echoAndReturnTitle = (arg) ->
console.log "echoing '#{arg}'"
console.log $(".explanation").text()
return document.title
Result:
> phantomjs inject.coffee
echoing 'hello'
PhantomJS is a headless WebKit with JavaScript API.
It has fast and native support for various web standards:
DOM handling, CSS selector, JSON, Canvas, and SVG.
PhantomJS is created by Ariya Hidayat.
PhantomJS: Headless WebKit with JavaScript API
or if you prefer JavaScript (they're auto-generated and a little ugly):
`inject.js':
// Generated by CoffeeScript 1.3.1
(function() {
var page;
page = require('webpage').create();
page.onConsoleMessage = function(msg) {
return console.log(msg);
};
page.open("http://www.phantomjs.org", function(status) {
if (status === "success") {
return page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", function() {
if (page.injectJs("do.js")) {
page.evaluate(function() {
var title;
title = echoAndReturnTitle('hello');
return console.log(title);
});
return phantom.exit();
}
});
}
});
}).call(this);
do.js:
// Generated by CoffeeScript 1.3.1
(function() {
window.echoAndReturnTitle = function(arg) {
console.log("echoing '" + arg + "'");
console.log($(".explanation").text());
return document.title;
};
}).call(this);
I would like my webpage to render faster. Based on this article, I understand that the page renders when the 'load' event is fired.
When I look at the Network Tab of my Chrome browser, I see that the 'load' event is fired after an ajax call to a PHP script returns.
Webpage is live at http://www.99like.com/index.php
=> Is there any way to get the page to render before the PHP script is called?
Following is the extract of the code which I think is relevant for the question:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="highcharts.js"></script>
<script type="text/javascript">
var ajax_load = "<img class='loading' src='images/load.gif' alt='loading...' />";
var inputForm = "<div class='shadow'><form type='submit' onsubmit='displayChart(); return false'><input id='searchBox' type='text' size='30' value='search keyword' /></form></div>";
var chart = "<div id='chart' class='shadow'></div>";
var chartPage = inputForm + chart;
$(function ()
{
exampleChart();
});
function exampleChart() {
$('#searchBox').val("hotel"); // nice example
displayChart ();
}
function displayChart () {
var keyword = $('#searchBox').val();
var chart = new Highcharts.Chart({ ... });
chart.showLoading();
var phpFunctionURL = "getChartData.php";
var DataSeries;
$.ajax( {
url: phpFunctionURL,
dataType: 'json',
async: false,
data: { ... },
success: function(json) { DataSeries = json; }
} );
}
</script>
A few remarks:
Make sure all your JavaScript are at the bottom of the page, including JQuery and Google Analytics code
Your web-page is missing the end tag
If needed, you could wait for the onLoad event to launch your AJAX request instead of the DomReady event, this will speed up the page rendering.
Looks like your AJAX request is synchronous. Making it asynchronous will solve the problem.
$.ajax( {
url: phpFunctionURL,
dataType: 'json',
async: true, // Changed from false to true
data: { ... },
success: function(json) { DataSeries = json; }
} );