I'm trying to create a simple widget test in Flutter. I have a custom widget that receives some values, composes a string and shows a Text with that string. I got to create the widget and it works, but I'm having trouble reading the value of the Text component to assert that the generated text is correct.
I created a simple test that illustrates the issue. I want to get the text value, which is "text". I tried several ways, if I get the finder asString() I could interpret the string to get the value, but I don't consider that a good solution. I wanted to read the component as a Text so that I have access to all the properties.
So, how would I read the Text widget so that I can access the data property?
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('my first widget test', (WidgetTester tester) async {
await tester
.pumpWidget(MaterialApp(home: Text("text", key: Key('unit_text'))));
// This works and prints: (Text-[<'unit_text'>]("text"))
var finder = find.byKey(Key("unit_text"));
print(finder.evaluate().first);
// This also works and prints: (Text-[<'unit_text'>]("text"))
var finderByType = find.byType(Text);
print(finderByType.evaluate().first);
// This returns empty
print(finder.first.evaluate().whereType<Text>());
// This throws: type 'StatelessElement' is not a subtype of type 'Text' in type cast
print(finder.first.evaluate().cast<Text>().first);
});
}
I got it working. I had to access the widget property of the Element, and then cast it as text:
var text = finder.evaluate().single.widget as Text;
print(text.data);
Please check this simple example.
testWidgets('Test name', (WidgetTester tester) async {
// findig the widget
var textFind = find.text("text_of_field");
// checking widget present or not
expect(textFind, findsOneWidget);
//getting Text object
Text text = tester.firstWidget(textFind);
// validating properies
expect(text.style.color, Colors.black);
...
...
}
You can use find.text
https://flutter.io/docs/cookbook/testing/widget/finders#1-find-a-text-widget
testWidgets('finds a Text Widget', (WidgetTester tester) async {
// Build an App with a Text Widget that displays the letter 'H'
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Text('H'),
),
));
// Find a Widget that displays the letter 'H'
expect(find.text('H'), findsOneWidget);
});
Related
Is there a way to add custom text in testcafe reports?
I would like to add a short description in the reports, so when somebody else checks it to be able to understand what the test does (especially when the test is passed, only the test name appears in the report and I cannot write too much text in the name of the test,it makes no sense). The problem is that I have different functions checking some menus and only 1 test scenario. I would like to add a text to know which functions were called.
console.log('custom text') will write the text only in the console
this.write('custom text') used inside a async function gives an error.
class goThroughAllMenus{
constructor(){
}
async f_CheckHomeMenus() {
//Description: This function is going through all submenus under Home page
// and checks that the pages are 'up and running'.
this.write(`Running test for Home menu`)
await t
//Hjem
.click(StartPage.HomeMenus.menuHome)
.expect(StartPage.StartPage.StartSubMenu.exists).ok()
}
}
There is no built-in capability to pass custom data to the reporter. You can ping the already existing issue in the TestCafe repo. However, if you just want to send common information about the whole test block, you can use meta and create a custom reporter to show this information.
//reporter.js
export default function () {
return {
async reportTaskStart (startTime, userAgents, testCount) {
},
async reportFixtureStart (name, path, meta) {
},
async reportTestStart (name, meta) {
},
async reportTestDone (name, testRunInfo, meta) {
this.write(meta );
},
async reportTaskDone (endTime, passed, warnings, result) {
}
};
}
Also, you can create a fork of any existing reporter and enhance one.
I'm having trouble while trying to find a way to click the submenu option in this nav menu
The submenu option I want to click and the code of it
Is there a way of selecting it by it's content of it or by other alternative?
I tried await page.click(); but since i don't have any id, name or value to identify that option it automatically closes the chromium page
also tried clicking wit the content
const select = require('puppeteer-select'); const element = await select(page).getElement('div:contains(Negociação)'); await element.click(); didn't work either.
It is possible to filter certain elements by its text. Here is an example on how to select a table element and search in all its cells for a given text.
I started by writing 2 helper functions for getting text from an element and another to search text within an array of elements:
// returns text for an array of elements
async function findElementByText(elements, searchText) {
for(let el of elements) {
// get the text of the element and return
// element if text matches
const text = await getText(el)
console.log(`found text: "${text}", searchText: "${searchText}"`)
// alternatively you could use this for being a little more error tolerant
// depends on your use case and comes with some pitfalls.
// return text.toUpperCase().includes(searchText.toUpperCase())
// compare text and return element if matched
if(searchText === text) {
return el
}
}
// NO element found..
console.log('No element found by text', searchText)
return null
}
// returns text from a html element
async function getText(element) {
const textElement = await element.getProperty('textContent')
const text = await textElement.jsonValue()
// make sure to remove white spaces.
return text.trim()
}
With given helper functions you can now select the table and search within its td elements (the cells) .
// This selects the first found table. You may wanna grab the correct table by a classname.
const table = await page.$('table')
// select all cells in the table.
const cells = await table.$$('td')
// search in the cells for a given text. "cell" will be a JSHandle
// which can be clicked!
const searchText = 'text4'
const cell = await findElementByText(cells, searchText)
if(!cell) {
throw 'Cell with text ' + searchText + ' not found!!.'
}
console.log('clicking cell now.')
// click the element
await cell.click()
If you host the html yourselve it would make life easier by setting a classname OR id to the cells you wanna click. (only if allowed and possible ofc) .
In your next question you should provide the html as plaint text instead of an image. Plain text can easily be copied by other users to test and debug.
Feel free to leave a comment.
I want to get the whole link address, but with the hoverProvider, I can only get the part of hoverd range.
here is the activate code and the result
export function activate(context: vscode.ExtensionContext) {
// Use the console to output diagnostic information (console.log) and errors (console.error)
// This line of code will only be executed once when your extension is activated
const disposal = vscode.languages.registerHoverProvider("javascript", {
provideHover(document, position, token) {
const range = document.getWordRangeAtPosition(position);
const word = document.getText(range);
return new vscode.Hover(
word
);
},
});
context.subscriptions.push(disposal);
}
so how to get the whole link 'https://www.youtube.com'?
According to the documentation, the getWordRangeAtPosition method allows you to define a custom word definition with a regular expression. A good regular expression to match a URL can be found in this answer.
Thus, in your case, you'd simply have to add a regular expression to the getWordRangeAtPosition method:
provideHover(document, position, token) {
const URLregex = /(https?:\/\/)*[-a-zA-Z0-9#:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()#:%_\+.~#?&//=]*)?/gi;
const range = document.getWordRangeAtPosition(position, URLregex);
const word = document.getText(range);
return new vscode.Hover(word);
}
I am facing a scenario where the element tag name and attribute is changing from env to env, but the text content alone is unique.
Therefore I am not able to define any value for Selector('could not define anything here').
How could I write a path to locate the element ?
I don't see a direct solution for this, but a workaround that could solve your issue. You could have an object that holds environments specific data for you and which helps you for specific cases as the one that you seem to be confronted with. In this object, you could also store environment specific Selectors. This could, written in TypeScript, look as follows:
import { Selector } from "testcafe";
interface EnvironmentData {
envName: string;
myVariableSelector: Selector;
}
// Set up a list that contains environment specific data objects
const CONFIGS: EnvironmentData[] = [
{
envName: "MyEnv1",
myVariableSelector: Selector("my css selector 1").withText("my text 1")
},
{
envName: "MyEnv2",
myVariableSelector: Selector("my css selector 2").withText("my text 2")
},
{
envName: "MyEnv3",
myVariableSelector: Selector("my css selector 3").withText("my text 3")
}
]
// Assuming that you're CI for instance sets a environment variable ENVIRONMENT_NAME to
// any of the specific environments MyEnv1, MyEnv2 or MyEnv3
function getConfigForEnvironment(envDataSets: EnvironmentData[]): EnvironmentData {
const envData = envDataSets.find((c) => c.envName === process.env.ENVIRONMENT_NAME);
if (envData === undefined) {
console.error(`No suitable data for environment '${process.env.ENVIRONMENT_NAME}' found!`);
process.exit(1);
}
return envData;
}
// Determine the right object before the tests start
const envData = getConfigForEnvironment(CONFIGS);
fixture`My awesome tests`.page("myTestUrl");
test("My test", async (t) => {
// Make use of the object that holds the data for the desired environment
await t.expect(envData.myVariableSelector.exists).ok("Should be fine for any environment!");
});
I did it in a simpler way using the or operator (,)
i,e I wrote the selector with Selector('div,span').withText('Value')
The attribute div and span are changing with environment so I used , to pass both and it worked.
Is there a simpler list type than DataGrid that can be connected to a store for Dojo?
I would like the data abstraction of the store, but I don't need the header and cell stucture. I would like to be more flexible in the representation of the datalines, where maybe each line calls an function to get laid out...
You ask a really good question. I actually have a blog post that is still in draft form called "The DataGrid should not be your first option".
I have done a couple thing using the store to display data from a store in a repeated form.
I have manually built an html table using dom-construct and for each.
var table = dojo.create('table', {}, parentNode);
var tbody = dojo.create('tbody', {}, table); // a version of IE needs this or it won't render the table
store.fetch({ // this is a dojo.data.ItemFileReadStore, but you cana dapt to the dojo.Store API
query: {},
onComplete: function(itms) {
dojo.forEach(itms, function(itm, idx) {
var tr = dojo.create('tr', {}, tbody);
// use idx to set odd/even css class
// create tds and the data that goes in them
});
}
});
I have also created a repeater, where I have an html template in a string form and use that to instantiate html for each row.
var htmlTemplate = '<div>${name}</div>'; // assumes name is in the data item
store.fetch({ // this is a dojo.data.ItemFileReadStore, but you cana dapt to the dojo.Store API
query: {},
onComplete: function(itms) {
dojo.forEach(itms, function(itm, idx) {
var expandedHtml = dojo.replace(htmlTemplate, itm);
// use dojo.place to put the html where you want it
});
}
});
You could also have a widget that you instantiate for each item.