Flutter Widget Testing Tutorial For Beginners - Practical Guide

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
a widget test is a test focused on testing the behavior and appearance of the UI components in response to user actions let's widget test a simple counter app to understand the basics then we can widget test more complex apps so this is the app we are going to widget test whenever this increment counter button is clicked on the state of this text changes that means it increments by one whenever I click over here so first of all let's understand the code we have till now what I've done is separated my application logic of increment counter function and the counter variable in a separate class of its own and separated the UI from it separating the app logic from UI does not necessarily mean it will help us in widget testing it definitely helps in unit testing but just for consistency I've separated the application logic and the user interface so let's get started to widget test of course we need to test the UI related files we don't have to test the application logic because for that we've already got unit testing so we are going to create a test of this home page so to create a test we are going to right click on it then click on go to tests and here we get an option to create home page test and then we go get our boilerplate code which we need to edit in unit testing we saw that we used the test function so that we could unit test but in widget testing we have the function test widgets to test the same thing we don't use the test function for widget testing because it doesn't give us access to this tester we learn more about this tester as we move along but just remember that we cannot use test for widget test it's only for your net test so let's remove this out let's get started by giving a description to this test widgets so what is our description going to be as we know from the unit test tutorial we have to use the given when then format so let's type it out here given counter is zero when increment button is clicked then counter should be 1. right because we this is exactly what we want to test we want to test that the counter when the app starts is 0 and when increment button is clicked then the counter should be 1 because whenever I click over here the state increases by 1. so this is what I want to test and this is generally what's tested in widget testing of course if you want you can go ahead and check if app bar is correctly present if this text is there that means you have pushed the button this many times this text is there but it really doesn't matter because you really want to widget test when some user interaction takes place and you want to see if that user interaction happened correctly that means I want to test if this button is there and if this button is there when I click on it this increments so some user action took place and if I'm getting the correct output or not so after deciding what to visit test you can go to this callback function this part right here so that you actually start widget testing so let's put a comma here so that there's formatting and let's get started with it note that we have put async over here because the Callback here requires future void if it requires future void we cannot return anything from this function and this should be asynchronous whenever we visit test we always need to remember one thing we are going to test it in isolation that means whatever happens in the lib folder or any other folder doesn't matter to this function even if I create another test function over here it doesn't matter this test function should not affect this text test function so if you are doing it in isolation and if you are widget testing we need to make sure that we build a widget tree we need to build our own isolated widget tree which we can test right so to do that we are going to use this tester as I told we are going to discuss more about this because this is one of the most critical classes the widget tester class is one of the most critical classes while widget testing so to build a widget tree you can do tester dot pump widget there are multiple types of pump functions if you see we're going to discuss most of them but as of now let's focus on pump widget pump widget basically renders the UI whatever you pass in here it will build it for you and create a widget tree based on that so what do we want to test we want to test this home page so we can just copy this pass in my home page here import it and make sure to give a title but title doesn't really matter here because we are not going to test the app bar and check if the title is correct or not but of course you can do that as well so yeah let's just pass in a counter app as title and Mark this as const and if you see pump widget is of the type future void so you need to await it and that's why we had put asynchronous function over here so now that we have got our widget tree up and running so the next thing we want to check is if my app restarts the counter is 0 or not if it's 0 that means the widget tree has been rendered successfully so what we essentially want to do is in the widget tree that we have pumped over here in our isolated widget tree we want to check if there's a text that has 0 in it right we just want to take this entire screen because this is my home page and here I want to check if this text is actually zero or not not just this text if there's any text in the entire screen to be zero or not so to do that we have something known as find coming from flutter test package again this helps us to find some widgets and it gives us multiple methods for it if you see by doing fine dot you'll see multiple methods it gives using which you can find the correct widget so you can search by Icon by Key by Tool tip by type by widget you can do anything but we want to find a text that's why we are going to do find dot text and you pass in a zero so using this we got a finder it goes through this entire pumped widget and finds if there's a text of zero just for Simplicity I'm just going to store it in this variable and I'm going to name this counter and then I'm going to do expect that we already saw in unit test so here I'll pass encounter but what should be a matcher well for that we have something known as finds One widget so basically what this means is it will go through the entire widget tree check if there's zero in the text if there's a 0 in the text then it sees that if there's one widget that it found related to it if you have two widget we can use finds n widgets and pass in two over here if it does not have any widget we can use finds nothing so this helps us to ensure that we find one widget correctly or not and in this case we are actually finding one widget right in case of text being one we find nothing because initially when the app starts the text is zero when we increment then the text is going to be 1. so that time we are going to find one right I hope this was clear if you have any question let me know in the comment section so let's run this app and see if it works so I'm going to run this main function and you see we get an error why is this error there remember I told you that you're going to widget test in isolation if you're testing it in isolation just my home page will not be enough because it renders the scaffold but what about material app because that's how our widget tree is made right we always need to wrap our widgets with material app so here what we can do is go to home page test do command shift R wrap with a widget known as material app pass home to it and pass constant aware and save it now if you run you see this puts a tick mark over here if we go to this testing Tab and run it again you see all the tests have passed that means it finds a text widget which has zero in it if I just change it to 1 it will fail so let's try it out and you see we get an error and here is the error text finder zero widgets with text one because it did not find the text with one so this is enough if you want to again test it out what you can do is final counter 2 is equal to find dot text one expect counter 2 to have finds nothing that makes sense because it will go through the widget tree see if there's a text with one and then it will find nothing so we are expecting the right thing and yes of course you can have multiple expect functions in one single test widget so save it again restart and you see this passed so given counter is 0 has completed now we just want to tap the increment button and see if it works so whenever this is tab this should be one this should change so first of all we need to find a floating action button we cannot create an instance of a counter class like this so we cannot do final counter counter which is equal to counter we cannot do this because this is basically like unit testing we don't want to unit test we want to simulate user actions to simulate user actions we cannot just go ahead and call call some functions from this class or anything else so first of all we again need to find this Floating Action button on the screen and then tap it so to find a floating action button we know we have something known as fine we can use it again and our find Dot and what should we use here well we want to find a floating action button and there's only one Floating Action button on this entire screen so we can use find dot by type and then pass in Floating Action button here and not like this if you pass in a type you only need to pass this without creating an instance of this class however if you do find dot by widget then you need to pass the instance like this pass in the on pressed and then it will accept but anyways we can just do find dot by type so now we have access to the button so we can have final increment button which is equal to fine dot by type and now we want to click on this button whenever we want to click on something we can use this tester this tester basically helps us to simulate user actions like you know tapping dragging if you see over here we can have tester Dot tab it also has drag and a lot more functions we'll see when we test apps later on but for now we just click on Tab and here it wants a finder if it wants a finder we have a finder because increment button is of the type finder so we can take this increment button tap it over here and if you see this is also future void so you can do await tester.tap and now obviously since this has been tapped we have one to be expected so we can have final counter again but this time counter should be three and the text Data finding should be one so we can take this counter three pass it away and have finds One widget and similarly for find text 0 we are going to create a count of four variable and it should find nothing because now the text has changed from 0 to 1. so everything has changed so let's run this and see if it works and you see we are getting an error why are we getting an error it's again saying 0 widgets with text 1. here it did not find one widget with the text one but after clicking a button we have one right to understand why this happens we'll have to go to home page and analyze the increment counter button so we found the floating action button and then on tap was clicked on so it ran the on press function increment counter function was called and then on counter dot increment counter was called so it should increase by one right because increment counter increases by one so here's the problem it's calling such State aware whenever set state is called this build function rebuilds so this entire widget tree again runs and then only the changes reflected on the screen if we go to home page test it clicked on this button but such state was not called that means the build function was not again rebuilt so to make it work what we need to do is call tester dot pump widget right if we call Pump widget it will again rebuild the entire widget and then it will work but here's a problem if we call Pump widget again the my home page will be initialized again if it's initialized again a counter will again be zero so it will again have the text as 0 not 1. so we cannot call Pump widget instead we need to call Pump if we just call Pump and obviously awaited so after we call Pump It reruns the entire widget tree in our case the isolated widget tree and then it will work for us so let's run this function and you see there's a tick mark even if we go to testing a stick Mark that means the test has run successfully so remember whenever you have such State called in your function in your UI part always make sure to run tester.pump you need to add those behaviors by yourself so this was the entire widget test needed for counter app you can also widget test this app bar and see if app bar is actually present or not so to do that let's come over here and let's find by type and Abba so let's pass in an ABBA and just pass it to expect so we directly have we don't need to create a variable again and again so we expect dot find dot by type app bar and it should find One widget for it because there's only one map bar right and then let's run the build function again and it works that means app bar is currently there if you want to widget test for this text you can do it on your own but what I want to dive into right now is been to this counter app itself so what if we go to this home page so what I'm going to do here is create two Floating Action buttons so below this text I'm just going to add another Floating Action button remove this key and let's suppose we have this okay so we have two Floating Action buttons and they look exactly the same but in this case what I'm going to do is remove on pressed and save it like this so ah on pressed is an empty function over here but this on pressed is increment counter now I'm going to go to the home page test run it again and you see I'm getting an error why am I getting an error this error is there because when you did find.buy type Floating Action button it found two Floating Action buttons right on the entire screen there are two Floating Action buttons right so it found two and it doesn't know which one to tap so when there are two same widgets we need a special identifier so that we can correctly find the correct widget in this case we have key so whenever you have two widgets that are looking exactly the same you know if there's a text widget and there's another text widget everything is same the only difference you can make is key and that's why you can do find dot by key over here it's a unique identifier so you need to pass in key here and a key over here is increment counter I remove key from here the key is only present over here and have increment counter over here so it will go ahead and find the correct loading action button and tap it so let's go to the function here rerun it and if you see it runs again perfectly so keys are helpful this way in widget testing now that you have basic understanding of widget testing let's dive into a more complex example where we have some apis involved so the next app we are going to widget test is this app this is basically an app with an app bar and a list of 10 users showing in a list view Builder where the title is the user's name and subtitle is user's email let's take a look at the code just to get a better understanding here we have something known as a user repository where there is a function of fetch users this fetch users function goes through a URL a Json placeholder API fetches a list of users and if the status code is 200 that means everything is okay it's just going to convert that list into a user model and this user model has a from Json function and a two Json function this from Json function is going to help us to convert the Json that we get from this API into a user model and to Json will help us to convert it into a map and it will be very helpful while testing and if the status code is not 200 then it will throw an exception and tell that it failed to fetch the users and the home screen we are rendering a scaffold with an app bar users and aboard your future Builder where the future is future users and this is basically the fetch user's function and here we are checking that if snapshot has data that means the user's data is properly fetched then then it's going to show it in a list view Builder which has a list style where the title is user's name and subtitle is user's email else if if there's you know any error it will show the error and the error is most likely going to be failed to fetch users and if there's no error and no data that means loading is going on so a circular progress indicator is returned so this is the code we have till now we need to visit us this home screen so let's get started I'm going to right click again go to tests and create a home screen test so in the description you can write whatever but it should be descriptive I'm just going to take the easy way out I'm just going to write this place list of users you can also use the given when then convention whatever you like it it should just be descriptive with name as with title as name and subtitle as email and now first of all I just want to get my widget tree up and running so I'll do away tester dot pump widget and then I'm going to pass material app and person home which will be home screen and this will be constant all right so this pumps the widget on the screen now I have my own virtual or you can say isolated widget tree now next thing I want to understand is what should I see on the screen so to understand this better what I'm going to do is refresh the screen or refresh the app and if you see when I as soon as I restart my app what I see initially is a loading indicator I get the loading indicator because the data is being fetched from an external API if it is being fetched from an external API it will take some time and while we don't have any data or we don't have any error it's loading so we get a loading indicator so that's what we are expecting we are expecting a circular progress indicator so in the home screen the first thing that we are going to expect is your expect dot by type circular progress indicator right and we want one widget to be found for it so we have finds One widget true so if we get a circular progress indicator yeah we are good to go so let's run this and it should pass for us and it certainly does now for the next thing we want a list view to appear so for a list view to appear well we need access to fetch users right and as I've explained previously in the unit test video whenever you have some API calls or network calls you have to take it from The Constructor so even here the future users that we have we are going to take it from this Constructor here that's because if I you know just switch off my Wi-Fi and try to fetch something and just try to run my test as well my test will certainly fail that means my tests are not in complete isolation I've explained this in the unit test video that's the same logic we are applying here since we want access to Future users because we are going to pass in our own future users and then expect out of it we are just going to take it from this Constructor here so we have final list of user if you don't understand it till now let me know in the comment section but it's the same logic as unit testing if you've understood it properly so we have required this dot future users I'm going to remove this variable from here I'm going to remove this call from here as well and in the future here we are going to have widget dot future users and this is going to be final future list of user not just list of user okay and now I can go to the main.dot file in the home screen remove constant and here the future users is going to be user repository dot fetch users correct now the app is going to work just as before you see it's working same but now whenever we call home screen and home screen test will be able to pass in our own list of users which we have control over we have control over them because we are going to manually create them so let's create it we are going to have final users which is equal to a list and here we're going to have a first user ID is going to be one name is suppose everyone and email is gmail.com then we have second ID is 2 let's say Naman Naman another gmail.com and then create a function for this so we are going to have future list of user mock fetch users we are basically mocking with our own values so we are just having Mock and then we'll put asynchronous that's why we created a function right this was just a list of users we need to pass in future list of user that's why we are creating a function then we are putting asynchronous so that we can convert it into a future and then you're returning users itself now I can take this function and pass it so yeah future users and pass it over here this is not going to be a constant anymore and it works so now we have access to our own list of users great now after I get a circular progress indicator I want to expect a list view but how am I directly going to have a list view right I mean if I'm getting a circular progress indicator there's going to be no list view how am I going to make sure that list view is there so for that we are going to use tester dot pump right if we use pump I hope you'll be able to get it so let's try it out so we have awaitester.pump and then we are going to have expect find dot by type list View finds One widget I hope this was easy to understand so let's run it and see if it works and it does work but just to give you a demo of when it would not work let me show something to you so here we are going to have future dot delayed duration will be suppose seconds one and then I return a list of not unless sorry a function where it returns user okay and then I'm returning it from here so we are basically having a future year even if you don't put anything now it's fine because we are returning a future at the end of the day and this delay is basically a one second delay where after one second we'll return users list now if I run this function you see it failed while it fail it's saying that list view was not found here but we are we were expecting unless you why did that happen well that's because initially we are not passed a duration of one second we had basically said that this is an asynchronous function and we are returning it there was no delay but what if there is a delay in the future and it comes after two seconds or one second when there's one second delay pump will not work because pump is basically rebuilding this entire widget tree if it's building this entire widget tree it really doesn't matter because a future Builder has some other plans just after rebuilding our future Builder is not going to have any data with it only after the future completes will it have any data with it so a simple set State what basically pump does is not enough that's why we have another method called pump and set it this basically keeps calling pump if you go where you'll see binding dot pump it keeps on calling pump until the future is resolved so let's try to run it and you see it works so whenever you have something like this a future or something which you want to resolve you always need to call Pump and settle this is also very handy when it comes to animations which we are going to test in our next app we can't use pump in that case because it just triggers one rebuild it does not keep calling until we get a satisfactory result so now that we've found list view I also want to expect another thing which is less Style I want to see if list tells us correctly generated so here I'm going to have expect find dot by type and then pass in list hi and in the matchup what are we going to expect how many listiles are there well there are 10 over here should I pass in 10 not really because I've passed in my own list of users right if I've passed in my own list of users it's going to be 2. so we are going to have fines n widgets and that is going to be users dot link so this means it will find two widgets of list Style now let's see if this works and it certainly does that means it's working now the final thing that I want to do here is basically check if the name the title and the subtitle are correctly being rendered how can I do that well I have a list of users right I can Loop through each one of them and check if the user name is present and the user email is present if it's correctly present that means our app is proper so we can just have for Loop where I say four variable user and users or final user and user whatever way you prefer expect find dot text so I basically want to find a text right I want to find a text that says user not name is there so we are going to have user dot name finds One widget then we are going to have another one where there's user.email and it finds One widget and it will do that for all the user present in the users list now if you run it let's see if it works and it does so this is one way of testing a widget that has data being fetched from an external API of course there's another way for it and it's much more complex than this which is basically including mocking from makito package stubbing the behavior of HTTP client whatever we learned in unit testing similar to that quite complex and I don't want to burden you with this because this is frankly a better way of doing it it's much more simpler and it's testable code so the next app that we are going to visit test is an animated app let me show you the demo so if I just restart my app you see this container went from height and width being 0 to the height being somewhat like 200 even the Border radius if you see went from being a perfect square to somewhat roundish and the color change from blue to green this is what we want to widget test let's take a look at the code we only have one screen here no repository classes nothing it's just a UI file that we want to unit test or more specifically a UI class so here we have animation controller and we've created an animation variable for width color and Border radius then in the NH State we are adding values to it here the vsync is this that's why we had to add with single tick provider State mix in something you need to do while you're animating then you have a with animation and then there's a tween tween is basically like a keyframe if you're coming from web development you basically provide it a starting and ending value then you pass in the type of Animation you want then you pass in the curve that you wanted to see in the animation on obviously pass in the controller and based on that it will animate from the starting value to the ending value which you can use in your UI down here similarly we have for color which is color tween and Border radius which is a tween of double because in color we are animating the color that's why color tween when we are having with on Border radius we only want to change the integer value and then you're calling controller dot forward which basically means start the animation process then we have disposed just to dispose of the controller then we have a scaffold and in the body we have animated Builder where we have to pass in the controller and the Builder and here we have passed in the with animation.value which is the width and height so it's a square then we have box decoration where the color is basically this part right here and the Border radius which is this part so animated Builder is used basically so that whenever there's changes in the value of this animation it rebuilds this part again and again so I hope the code was understood now we just want to widget test this so let's go ahead right click go to tests then we'll create an animation screen test and here you can type anything I'm just going ahead with animation screen test and if you want you can write more descriptive you get the point now so let's get started first of all we always know we want to pump the widget on the screen so we have awaitester.pump widget then we will pass in the widget which is material app obviously don't forget that so we have constant material app then in the home we have to pass in animation screen and save it that's pretty much it then after that we want to test the initial state of the container over here what does that mean well basically we have a container right now whenever I start my app you'll see it starts from almost zero so that's what I want to test here I want to check if the container is really having a width and height of 0 and the color is actually blue with border radius of whatever that was mentioned over here let's say zero I want to check all of these conditions here and if you want to test it better what you can do is pass in 50 over here as the width and then you'll be able to test it out even better you see now you get a better view of what's happening the initial state is height 50 the initial border radius is 0 and the initial color is blue that's what I want to test so basically I want to get access to the container first and when I get access to the container I can take the properties of the container and test it out so first to get a container we want to First verify if the container is actually there or not so we have expect fine dot by type container and in the matcher you'll pass in finds One widget because we should be seeing one widget right and run it if you come to the testing tab you see the test has run successfully the widget test has failed and that's obvious because we have deleted the file so we'll delete the test file as well so come back here and let's run the tests and you see the test has passed great now that we have access to container let's make sure to use some properties on it so I can take this variable out let's call this final container let's not name it container type let's name it container finder which is equal to find dot by type container okay now I can take this and pass it over here and now we know this will pass so now I want to expect values based on this container so I have container finder dot but if you see I don't get any properties of container as such why because this container finder is actually a finder it's not really a widget it's not a container widget it's a finder we need to convert this finder into a particular widget so that we can call properties on it how will we do that well for that we'll have to use tester again so we'll use this tester and half tester Dot widget when you do tester.widget it requires a finder and if you see it finds the matching widget in the widget tree so you just need to go ahead pass in the container finder here and then it will return to you a widget and that will be the container widget so if I just have final container which is equal to tester.widget and however this this is of the type widget so now I can use container to call properties of container on it so if I do container dot I still don't get container you can say Auto suggestion why is that the case now well that's because this is a widget right this is not a container so how will I get access to container properties this is giving me access to widget properties all of these properties that you see here are related to widget for example create element so I need to make sure that this is a widget so to do that I'll have to pass in a generic type here and call this container because if you see T is mentioned over here whenever you have t you basically have something to pass over here which should be a widget and now if you hover over this this is of the type container and now you can call properties of container and obviously you can also call properties of widget because container extends widget so I hope that was clear now you can just do your thing container dot well first of all I want to check the width the width should be 50 so we have container dot width but it's not giving me access to width that's why we'll have to use something else and that something else can be container dot constraints which is of the type box constraints and you can see this is nullable so we are going to make sure that this is not going to be null and then we can pass in width so the width can be minimum width or maximum width in our case both is the same Max and Min width are the same thing so you can use either of them let's go ahead with minimum width and make sure to use 50 over here because our container starts from 50 right if you see over here let me just restart it it starts from 50. and the same thing will happen for height as well it's going to be minimum height and that is going to be 50 as well so let's save it restart the test and see if it works and you see the test has passed that means we are able to get the height and width correctly now if you just want to fail a test you can have 150 and see if it fails and you see the test has failed sometimes it's good to fail your test purposely so that you know there's no bug or anything at all going on so okay we add the test now now let's test the Border radius and the color of the container so we are going to have expect container dot color and that should be colors.blue in the start it's blue right and after that expect again which is going to have container dot border radius but there's no property of Border radius because border radius is a property in decoration so we are going to call container dot decoration dot border radius but it's still not there it's not there because this is of the type decoration and Border radius is present in a class of box decoration but yeah box decoration finally extends decoration but it's not decoration it's going to have an extra property of water radius which decoration does not have so to make this work what you need to do is treat this as box decoration so once you treat the susbox decoration which will work because decoration and box decoration are similar because box decoration extends decoration and now you can call border radius property on it your container takes type of decoration because there are multiple types of decoration one of them is box decoration so here we are treating a task box decoration calling the Border radius property and initially it should be zero so this is all about initial let's put a comment for our own understanding let's run it and see if it works and it doesn't work and now we are getting this error saying it expected a value of color this but the actual value was none why was it null because whenever you see whenever we've called decoration property we weren't able to call color over here whenever we called color aware so let's say I typed in colors dot blue over here and restarted the app you see we get this error we get the server because there's box decoration and box decoration has a property of color but in container color is already present so when you have box decoration you cannot call this property and this color property has to be null that's why again here you cannot use container.color instead you have to use container dot decoration as box decoration and call the color property after that and then it will have some value only then will it not be null so let's run this and see if it works and again it didn't work let's see so here it's saying that the expected value was 0 we had passed in 0 but the actual value received was border radius.0 and that's true because border radius is of the type border radius geometry it cannot give an integer value so here what we'll have to do here is pass in your border radius Dot zero so when you pass in Border radius.0 you're basically saying border radius dot all radius.0 that means border radius from all side is zero so let's start and run it again and it successfully passes so initial state is now done now finally we want to trigger a frame change that means we want to move the controller forward and we want to move it so forward that we are at the end of the animation that means the animation has got over so to do that well what will we do we've already seen this method tester dot pump and set it we cannot call pump because that would mean only calling such State and that doesn't mean the animation gets over pump and settle will probably do that so let's have a weightier and again put all the expect conditions so just for Simplicity you might want to type this out I'll just paste it again the minimum width and height is now going to be 200 the color is going to be green and Border radius is going to be let's see what we have here border radius dot circular and the value here is going to be 50. because that's the end value so we'll pass 50 over here save it restart and uh test Fields again so this exception occurs because the expected value is 200 but actually we are getting 50. why is that 50 there we have called pumped unsettled right but that's because we are still using the container that we found over here you see in the widget tree when we initially started we had this container then we created a widget out of it and then we expected the initial value of the container based on that and that's right but after we call tester.pump and settle the width height color everything of this container changes so it's not the same container as this one so we have to update this so to update it we are going to use the same variable let's make them where because we need to change it if we make it final we won't be able to change and here we are just going to have container Dot container finder which is equal to the same thing so we are having the same thing again but this time we are again going through the widget tree meaning we had the container finder we expected the initial value based on it then we called tester.pump and settle after we call Pump and settle the height width color border radius everything of the container changes so it's not the previous container again so we again need to find the container from the widget tree make a widget out of it because this is a finder and then use it for our expectations now if we run it our test pass so I hope this was clear this is what I wanted to point out and you can see pump and settle again worked If instead you use pump over and run the app you see the test have failed and it's saying the actual value is 50. so whenever you have animation or something you use pump and settle whenever you have a simple rebuild to happen you use pump and pump widget is so that you build the widget tree so this was all about testing an animated app so this was it for this tutorial thank you so much for watching and I'll see you in the next video
Info
Channel: Rivaan Ranawat
Views: 12,857
Rating: undefined out of 5
Keywords: flutter, flutter tutorial, flutter tutorial for beginners, flutter testing tutorial, flutter testing, flutter widget test, flutter widget testing, flutter widget test for beginners, flutter for beginners, flutter widget testing for beginners, flutter automated testing, flutter automated test
Id: 6usqzoKYXag
Channel Id: undefined
Length: 46min 13sec (2773 seconds)
Published: Thu Mar 16 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.