diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..48ff912 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Autosave files +*~ + +# build +[Oo]bj/ +[Bb]in/ +packages/ +TestResults/ + +# globs +Makefile.in +*.DS_Store +*.sln.cache +*.suo +*.cache +*.pidb +*.userprefs +*.usertasks +config.log +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.user +*.tar.gz +tarballs/ +test-results/ +Thumbs.db + +# Mac bundle stuff +*.dmg +*.app + +# resharper +*_Resharper.* +*.Resharper + +# dotCover +*.dotCover + +Droid/Resources/Resource.designer.cs + +.vs diff --git a/src/Droid/Assets/AboutAssets.txt b/src/Droid/Assets/AboutAssets.txt new file mode 100644 index 0000000..a9b0638 --- /dev/null +++ b/src/Droid/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with your package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); diff --git a/src/Droid/MainActivity.cs b/src/Droid/MainActivity.cs new file mode 100644 index 0000000..d7a92cc --- /dev/null +++ b/src/Droid/MainActivity.cs @@ -0,0 +1,33 @@ +using System; + +using Android.App; +using Android.Content; +using Android.Content.PM; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Android.OS; + +namespace UITestDemo.Droid +{ + [Activity(Label = "UITestDemo.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] + public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity + { + protected override void OnCreate(Bundle bundle) + { + TabLayoutResource = Resource.Layout.Tabbar; + ToolbarResource = Resource.Layout.Toolbar; + + base.OnCreate(bundle); + + global::Xamarin.Forms.Forms.Init(this, bundle); + + LoadApplication(new App()); + } + + protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) + { + base.OnActivityResult(requestCode, resultCode, data); + } + } +} diff --git a/src/Droid/Properties/AndroidManifest.xml b/src/Droid/Properties/AndroidManifest.xml new file mode 100644 index 0000000..3131983 --- /dev/null +++ b/src/Droid/Properties/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Droid/Properties/AssemblyInfo.cs b/src/Droid/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5a134a2 --- /dev/null +++ b/src/Droid/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using Android.App; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("UITestDemo.Droid")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("${AuthorCopyright}")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/src/Droid/Resources/AboutResources.txt b/src/Droid/Resources/AboutResources.txt new file mode 100644 index 0000000..10f52d4 --- /dev/null +++ b/src/Droid/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/src/Droid/Resources/drawable-hdpi/icon.png b/src/Droid/Resources/drawable-hdpi/icon.png new file mode 100644 index 0000000..964f110 Binary files /dev/null and b/src/Droid/Resources/drawable-hdpi/icon.png differ diff --git a/src/Droid/Resources/drawable-xhdpi/icon.png b/src/Droid/Resources/drawable-xhdpi/icon.png new file mode 100644 index 0000000..3c01e60 Binary files /dev/null and b/src/Droid/Resources/drawable-xhdpi/icon.png differ diff --git a/src/Droid/Resources/drawable-xxhdpi/icon.png b/src/Droid/Resources/drawable-xxhdpi/icon.png new file mode 100644 index 0000000..0d8c1c5 Binary files /dev/null and b/src/Droid/Resources/drawable-xxhdpi/icon.png differ diff --git a/src/Droid/Resources/drawable/icon.png b/src/Droid/Resources/drawable/icon.png new file mode 100644 index 0000000..b0ba715 Binary files /dev/null and b/src/Droid/Resources/drawable/icon.png differ diff --git a/src/Droid/Resources/drawable/xamarin_logo.png b/src/Droid/Resources/drawable/xamarin_logo.png new file mode 100644 index 0000000..b36d00e Binary files /dev/null and b/src/Droid/Resources/drawable/xamarin_logo.png differ diff --git a/src/Droid/Resources/layout/Tabbar.axml b/src/Droid/Resources/layout/Tabbar.axml new file mode 100644 index 0000000..0bc7e9d --- /dev/null +++ b/src/Droid/Resources/layout/Tabbar.axml @@ -0,0 +1,2 @@ + + diff --git a/src/Droid/Resources/layout/Toolbar.axml b/src/Droid/Resources/layout/Toolbar.axml new file mode 100644 index 0000000..d685cba --- /dev/null +++ b/src/Droid/Resources/layout/Toolbar.axml @@ -0,0 +1,2 @@ + + diff --git a/src/Droid/Resources/values/styles.xml b/src/Droid/Resources/values/styles.xml new file mode 100644 index 0000000..3dc5ef6 --- /dev/null +++ b/src/Droid/Resources/values/styles.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/Droid/UITestDemo.Droid.csproj b/src/Droid/UITestDemo.Droid.csproj new file mode 100644 index 0000000..edd5421 --- /dev/null +++ b/src/Droid/UITestDemo.Droid.csproj @@ -0,0 +1,257 @@ + + + + + Debug + AnyCPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + UITestDemo.Droid + UITestDemo.Droid + v9.0 + True + Resources\Resource.designer.cs + Resource + Properties\AndroidManifest.xml + Resources + Assets + false + armeabi-v7a;armeabi;x86;arm64-v8a;x86_64 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + None + true + x86;arm64-v8a;armeabi-v7a;x86_64 + true + + + true + pdbonly + true + bin\Release + prompt + 4 + true + armeabi-v7a;x86;arm64-v8a;x86_64 + + + + + + + + + ..\packages\Xamarin.Android.Support.Annotations.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Annotations.dll + + + + ..\packages\Xamarin.Android.Arch.Core.Common.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Core.Common.dll + + + ..\packages\Xamarin.Android.Arch.Core.Runtime.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Core.Runtime.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Common.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.Common.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.LiveData.Core.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.LiveData.Core.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.LiveData.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.LiveData.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Runtime.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.Runtime.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.ViewModel.1.1.1.1\lib\monoandroid90\Xamarin.Android.Arch.Lifecycle.ViewModel.dll + + + ..\packages\Xamarin.Android.Support.Collections.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Collections.dll + + + ..\packages\Xamarin.Android.Support.CursorAdapter.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CursorAdapter.dll + + + ..\packages\Xamarin.Android.Support.DocumentFile.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.DocumentFile.dll + + + ..\packages\Xamarin.Android.Support.Interpolator.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Interpolator.dll + + + ..\packages\Xamarin.Android.Support.LocalBroadcastManager.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.LocalBroadcastManager.dll + + + ..\packages\Xamarin.Android.Support.Print.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Print.dll + + + ..\packages\Xamarin.Android.Support.v7.CardView.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v7.CardView.dll + + + ..\packages\Xamarin.Android.Support.VersionedParcelable.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.VersionedParcelable.dll + + + ..\packages\Xamarin.Android.Support.Compat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Compat.dll + + + ..\packages\Xamarin.Android.Support.AsyncLayoutInflater.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.AsyncLayoutInflater.dll + + + ..\packages\Xamarin.Android.Support.CustomView.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CustomView.dll + + + ..\packages\Xamarin.Android.Support.CoordinaterLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CoordinaterLayout.dll + + + ..\packages\Xamarin.Android.Support.DrawerLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.DrawerLayout.dll + + + ..\packages\Xamarin.Android.Support.Loader.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Loader.dll + + + ..\packages\Xamarin.Android.Support.Core.Utils.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Core.Utils.dll + + + ..\packages\Xamarin.Android.Support.Media.Compat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Media.Compat.dll + + + ..\packages\Xamarin.Android.Support.SlidingPaneLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.SlidingPaneLayout.dll + + + ..\packages\Xamarin.Android.Support.SwipeRefreshLayout.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.SwipeRefreshLayout.dll + + + ..\packages\Xamarin.Android.Support.v7.Palette.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v7.Palette.dll + + + ..\packages\Xamarin.Android.Support.Vector.Drawable.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Vector.Drawable.dll + + + ..\packages\Xamarin.Android.Support.ViewPager.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.ViewPager.dll + + + ..\packages\Xamarin.Android.Support.Core.UI.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Core.UI.dll + + + ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Animated.Vector.Drawable.dll + + + ..\packages\Xamarin.Android.Support.CustomTabs.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.CustomTabs.dll + + + ..\packages\Xamarin.Android.Support.Fragment.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Fragment.dll + + + ..\packages\Xamarin.Android.Support.Transition.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Transition.dll + + + ..\packages\Xamarin.Android.Support.v4.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v4.dll + + + ..\packages\Xamarin.Android.Support.v7.AppCompat.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v7.AppCompat.dll + + + ..\packages\Xamarin.Android.Support.v7.RecyclerView.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v7.RecyclerView.dll + + + ..\packages\Xamarin.Android.Support.Design.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.Design.dll + + + ..\packages\Xamarin.Android.Support.v7.MediaRouter.28.0.0.1\lib\monoandroid90\Xamarin.Android.Support.v7.MediaRouter.dll + + + ..\packages\Xamarin.Forms.4.1.0.673156\lib\MonoAndroid90\FormsViewGroup.dll + + + ..\packages\Xamarin.Forms.4.1.0.673156\lib\MonoAndroid90\Xamarin.Forms.Core.dll + + + ..\packages\Xamarin.Forms.4.1.0.673156\lib\MonoAndroid90\Xamarin.Forms.Platform.Android.dll + + + ..\packages\Xamarin.Forms.4.1.0.673156\lib\MonoAndroid90\Xamarin.Forms.Platform.dll + + + ..\packages\Xamarin.Forms.4.1.0.673156\lib\MonoAndroid90\Xamarin.Forms.Xaml.dll + + + ..\packages\Newtonsoft.Json.12.0.2\lib\netstandard2.0\Newtonsoft.Json.dll + + + ..\packages\Xam.Plugin.Connectivity.3.2.0\lib\MonoAndroid10\Plugin.Connectivity.Abstractions.dll + + + ..\packages\Xam.Plugin.Connectivity.3.2.0\lib\MonoAndroid10\Plugin.Connectivity.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Droid/appcenter-post-build.sh b/src/Droid/appcenter-post-build.sh new file mode 100644 index 0000000..0d16772 --- /dev/null +++ b/src/Droid/appcenter-post-build.sh @@ -0,0 +1,37 @@ +# Environment Variables created by App Center +# $APPCENTER_SOURCE_DIRECTORY +# $APPCENTER_OUTPUT_DIRECTORY +# $APPCENTER_BRANCH + +# Custom Environment Variables +# $API_KEY +# $TEAM_APP +# $DEVICE_SET +UITEST_PATH='Xamarin.UITest/UITestDemo/UITestDemo.UITest' + +# DEBUGGING +# echo "Hello World! I'm a post-build script!" +# echo "I run at the end of your build." +# echo "Documentation: https://docs.microsoft.com/en-us/appcenter/build/custom/scripts/#post-build" +# echo "Referencing source directory: " $APPCENTER_SOURCE_DIRECTORY +# echo "Contents: " +# ls $APPCENTER_SOURCE_DIRECTORY +# echo "& output directory: " $APPCENTER_OUTPUT_DIRECTORY +# echo "Contents: " +# ls $APPCENTER_OUTPUT_DIRECTORY + +# Build UITest project +eval MSBuild $APPCENTER_SOURCE_DIRECTORY/$UITEST_PATH -v:q + +# DEBUGGING +# echo "contents of UITest directory:" +# ls $APPCENTER_SOURCE_DIRECTORY/$UITEST_PATH + +# Upload tests +App_Center_Test_Command='appcenter test run uitest --app $TEAM_APP --devices $DEVICE_SET --app-path $APPCENTER_OUTPUT_DIRECTORY/com.companyname.UITestDemo.apk --test-series "gh-$APPCENTER_BRANCH" --locale "en_US" --build-dir $APPCENTER_SOURCE_DIRECTORY/$UITEST_PATH/bin/Debug --async --token $API_KEY --uitest-tools-dir $APPCENTER_SOURCE_DIRECTORY/Xamarin.UITest/UITestDemo/packages/Xamarin.UITest.*/tools' + +echo $App_Center_Test_Command +eval $App_Center_Test_Command + +# End +echo "end post-build script" \ No newline at end of file diff --git a/src/Droid/appcenter-post-clone.sh b/src/Droid/appcenter-post-clone.sh new file mode 100644 index 0000000..6f97201 --- /dev/null +++ b/src/Droid/appcenter-post-clone.sh @@ -0,0 +1,9 @@ +# Comments + +echo "Hello World! I'm a post-clone script!" + +echo "I run immediately after your project has finished cloning." + +echo "Documentation: https://docs.microsoft.com/en-us/appcenter/build/custom/scripts/#post-clone" + +echo "end post-clone script" \ No newline at end of file diff --git a/src/Droid/appcenter-pre-build.sh b/src/Droid/appcenter-pre-build.sh new file mode 100644 index 0000000..284e12d --- /dev/null +++ b/src/Droid/appcenter-pre-build.sh @@ -0,0 +1,9 @@ +# Comments + +echo "Hello World! I'm a pre-build script!" + +echo "I execute after your repo is cloned and dependencies are restored; but before the build actually occurs." + +echo "Documentation: https://docs.microsoft.com/en-us/appcenter/build/custom/scripts/#pre-build" + +echo "end pre-build script" \ No newline at end of file diff --git a/src/Droid/packages.config b/src/Droid/packages.config new file mode 100644 index 0000000..28021d4 --- /dev/null +++ b/src/Droid/packages.config @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UITestDemo.UITest/AppInitializer.cs b/src/UITestDemo.UITest/AppInitializer.cs new file mode 100644 index 0000000..cde6917 --- /dev/null +++ b/src/UITestDemo.UITest/AppInitializer.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using System.Linq; +using Xamarin.UITest; +using Xamarin.UITest.Queries; + +namespace UITestDemo.UITest +{ + public class AppInitializer + { + public static IApp StartApp(Platform platform) + { + // TODO: If the iOS or Android app being tested is included in the solution + // then open the Unit Tests window, right click Test Apps, select Add App Project + // and select the app projects that should be tested. + // + // The iOS project should have the Xamarin.TestCloud.Agent NuGet package + // installed. To start the Test Cloud Agent the following code should be + // added to the FinishedLaunching method of the AppDelegate: + // + // #if ENABLE_TEST_CLOUD + // Xamarin.Calabash.Start(); + // #endif + if (platform == Platform.Android) + { + return ConfigureApp + .Android + // TODO: You must use .ApkFile() if: + // a) runnning on Windows + // b) or your Android project is not referenced by the UITest project. + //.ApkFile ("../../../precompiledApps/com.companyname.UITestDemo.apk") + .StartApp(); + } + + // Workaround for iOS simulator reset bug + Environment.SetEnvironmentVariable("UITEST_FORCE_IOS_SIM_RESTART", "1"); + + return ConfigureApp + .iOS + // TODO: Update this path to point to your iOS app and uncomment the + // code if the app is not included in the solution. + // The .AppBundle method is only supported for iOS simulators. + // .AppBundle ("../../../precompiledApps/UITestDemo.iOS.app") + // + // .InstalledApp requires you to build an IPA using the Debug + // configuration & a valid provisioning profile, and preinstalling + // it on the target device. + // .InstalledApp("com.companyname.UITestDemo") + .StartApp(); + } + } +} diff --git a/src/UITestDemo.UITest/Tests.cs b/src/UITestDemo.UITest/Tests.cs new file mode 100644 index 0000000..eafceea --- /dev/null +++ b/src/UITestDemo.UITest/Tests.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Linq; +using NUnit.Framework; +using Xamarin.UITest; +using Xamarin.UITest.Queries; + +namespace UITestDemo.UITest +{ + [TestFixture(Platform.Android)] + [TestFixture(Platform.iOS)] + public class Tests + { + IApp app; + Platform platform; + + public Tests(Platform platform) + { + this.platform = platform; + } + + [SetUp] + public void BeforeEachTest() + { + app = AppInitializer.StartApp(platform); + } + + [Test] + public void AppLaunches() + { + app.Screenshot("First screen."); + } + + [Test] + public void ClearTextDemo() + { + app.Tap(x => x.Marked("Add")); + app.Tap(x => x.Text("Item name")); + + app.Screenshot("Before calling ClearText"); + app.ClearText(); + app.EnterText("The test worked!"); + app.Screenshot("Text cleared & replaced"); + app.Back(); + } + } +} diff --git a/src/UITestDemo.UITest/UITestDemo.UITest.csproj b/src/UITestDemo.UITest/UITestDemo.UITest.csproj new file mode 100644 index 0000000..229dc80 --- /dev/null +++ b/src/UITestDemo.UITest/UITestDemo.UITest.csproj @@ -0,0 +1,60 @@ + + + + + + Debug + AnyCPU + {912248EA-0FED-48E1-8994-6C3080551B2C} + Library + UITestDemo.UITest + UITestDemo.UITest + v4.6.1 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + + + true + bin\Release + prompt + 4 + + + + + ..\packages\Xamarin.UITest.3.0.3\lib\net45\Xamarin.UITest.dll + + + ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + + + + + + + + + + + + {499F854C-5244-4E34-92C5-4072B83B84D7} + UITestDemo.iOS + False + False + + + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7} + UITestDemo.Droid + False + False + + + + \ No newline at end of file diff --git a/src/UITestDemo.UITest/packages.config b/src/UITestDemo.UITest/packages.config new file mode 100644 index 0000000..87b0ea2 --- /dev/null +++ b/src/UITestDemo.UITest/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/UITestDemo.sln b/src/UITestDemo.sln new file mode 100644 index 0000000..0008e44 --- /dev/null +++ b/src/UITestDemo.sln @@ -0,0 +1,78 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2026 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UITestDemo", "UITestDemo\UITestDemo.shproj", "{2933EC8A-6E14-4F77-BEF6-51ACEAFD1040}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITestDemo.iOS", "iOS\UITestDemo.iOS.csproj", "{499F854C-5244-4E34-92C5-4072B83B84D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITestDemo.Droid", "Droid\UITestDemo.Droid.csproj", "{3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UITestDemo.UITest", "UITestDemo.UITest\UITestDemo.UITest.csproj", "{912248EA-0FED-48E1-8994-6C3080551B2C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "upload scripts", "upload scripts", "{18CBDE2C-B150-4C7E-B99F-B1569ED96CB9}" + ProjectSection(SolutionItems) = preProject + android.sh = android.sh + ios.sh = ios.sh + EndProjectSection +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + UITestDemo\UITestDemo.projitems*{2933ec8a-6e14-4f77-bef6-51aceafd1040}*SharedItemsImports = 13 + UITestDemo\UITestDemo.projitems*{3b2fe7a6-e04b-4a11-9872-9ed1ffa093a7}*SharedItemsImports = 4 + UITestDemo\UITestDemo.projitems*{499f854c-5244-4e34-92c5-4072b83b84d7}*SharedItemsImports = 4 + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|iPhone = Debug|iPhone + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|Any CPU = Release|Any CPU + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {499F854C-5244-4E34-92C5-4072B83B84D7}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {499F854C-5244-4E34-92C5-4072B83B84D7}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator + {499F854C-5244-4E34-92C5-4072B83B84D7}.Debug|iPhone.ActiveCfg = Debug|iPhone + {499F854C-5244-4E34-92C5-4072B83B84D7}.Debug|iPhone.Build.0 = Debug|iPhone + {499F854C-5244-4E34-92C5-4072B83B84D7}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {499F854C-5244-4E34-92C5-4072B83B84D7}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {499F854C-5244-4E34-92C5-4072B83B84D7}.Release|Any CPU.ActiveCfg = Release|iPhone + {499F854C-5244-4E34-92C5-4072B83B84D7}.Release|Any CPU.Build.0 = Release|iPhone + {499F854C-5244-4E34-92C5-4072B83B84D7}.Release|iPhone.ActiveCfg = Release|iPhone + {499F854C-5244-4E34-92C5-4072B83B84D7}.Release|iPhone.Build.0 = Release|iPhone + {499F854C-5244-4E34-92C5-4072B83B84D7}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {499F854C-5244-4E34-92C5-4072B83B84D7}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|iPhone.Build.0 = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Release|Any CPU.Build.0 = Release|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Release|iPhone.ActiveCfg = Release|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Release|iPhone.Build.0 = Release|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3B2FE7A6-E04B-4A11-9872-9ED1FFA093A7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Debug|iPhone.Build.0 = Debug|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Release|Any CPU.Build.0 = Release|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Release|iPhone.ActiveCfg = Release|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Release|iPhone.Build.0 = Release|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {912248EA-0FED-48E1-8994-6C3080551B2C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {63409134-6C45-425B-B5D5-F645B9FB3D3D} + EndGlobalSection +EndGlobal diff --git a/src/UITestDemo/App.xaml b/src/UITestDemo/App.xaml new file mode 100644 index 0000000..bb06350 --- /dev/null +++ b/src/UITestDemo/App.xaml @@ -0,0 +1,18 @@ + + + + + #2196F3 + #1976D2 + #96d1ff + #FAFAFA + #C0C0C0 + #4d4d4d + #999999 + + + + diff --git a/src/UITestDemo/App.xaml.cs b/src/UITestDemo/App.xaml.cs new file mode 100644 index 0000000..24713de --- /dev/null +++ b/src/UITestDemo/App.xaml.cs @@ -0,0 +1,27 @@ +using System; + +using Xamarin.Forms; + +namespace UITestDemo +{ + public partial class App : Application + { + public static bool UseMockDataStore = true; + public static string BackendUrl = "https://localhost:5000"; + + public App() + { + InitializeComponent(); + + if (UseMockDataStore) + DependencyService.Register(); + else + DependencyService.Register(); + + if (Device.RuntimePlatform == Device.iOS) + MainPage = new MainPage(); + else + MainPage = new NavigationPage(new MainPage()); + } + } +} diff --git a/src/UITestDemo/Models/Item.cs b/src/UITestDemo/Models/Item.cs new file mode 100644 index 0000000..ede97ad --- /dev/null +++ b/src/UITestDemo/Models/Item.cs @@ -0,0 +1,11 @@ +using System; + +namespace UITestDemo +{ + public class Item + { + public string Id { get; set; } + public string Text { get; set; } + public string Description { get; set; } + } +} diff --git a/src/UITestDemo/Services/CloudDataStore.cs b/src/UITestDemo/Services/CloudDataStore.cs new file mode 100644 index 0000000..3a2c43b --- /dev/null +++ b/src/UITestDemo/Services/CloudDataStore.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using Plugin.Connectivity; + +namespace UITestDemo +{ + public class CloudDataStore : IDataStore + { + HttpClient client; + IEnumerable items; + + public CloudDataStore() + { + client = new HttpClient(); + client.BaseAddress = new Uri($"{App.BackendUrl}/"); + + items = new List(); + } + + public async Task> GetItemsAsync(bool forceRefresh = false) + { + if (forceRefresh && CrossConnectivity.Current.IsConnected) + { + var json = await client.GetStringAsync($"api/item"); + items = await Task.Run(() => JsonConvert.DeserializeObject>(json)); + } + + return items; + } + + public async Task GetItemAsync(string id) + { + if (id != null && CrossConnectivity.Current.IsConnected) + { + var json = await client.GetStringAsync($"api/item/{id}"); + return await Task.Run(() => JsonConvert.DeserializeObject(json)); + } + + return null; + } + + public async Task AddItemAsync(Item item) + { + if (item == null || !CrossConnectivity.Current.IsConnected) + return false; + + var serializedItem = JsonConvert.SerializeObject(item); + + var response = await client.PostAsync($"api/item", new StringContent(serializedItem, Encoding.UTF8, "application/json")); + + return response.IsSuccessStatusCode; + } + + public async Task UpdateItemAsync(Item item) + { + if (item == null || item.Id == null || !CrossConnectivity.Current.IsConnected) + return false; + + var serializedItem = JsonConvert.SerializeObject(item); + var buffer = Encoding.UTF8.GetBytes(serializedItem); + var byteContent = new ByteArrayContent(buffer); + + var response = await client.PutAsync(new Uri($"api/item/{item.Id}"), byteContent); + + return response.IsSuccessStatusCode; + } + + public async Task DeleteItemAsync(string id) + { + if (string.IsNullOrEmpty(id) && !CrossConnectivity.Current.IsConnected) + return false; + + var response = await client.DeleteAsync($"api/item/{id}"); + + return response.IsSuccessStatusCode; + } + } +} diff --git a/src/UITestDemo/Services/IDataStore.cs b/src/UITestDemo/Services/IDataStore.cs new file mode 100644 index 0000000..33af80a --- /dev/null +++ b/src/UITestDemo/Services/IDataStore.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace UITestDemo +{ + public interface IDataStore + { + Task AddItemAsync(T item); + Task UpdateItemAsync(T item); + Task DeleteItemAsync(string id); + Task GetItemAsync(string id); + Task> GetItemsAsync(bool forceRefresh = false); + } +} diff --git a/src/UITestDemo/Services/MockDataStore.cs b/src/UITestDemo/Services/MockDataStore.cs new file mode 100644 index 0000000..b308b47 --- /dev/null +++ b/src/UITestDemo/Services/MockDataStore.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace UITestDemo +{ + public class MockDataStore : IDataStore + { + List items; + + public MockDataStore() + { + items = new List(); + var mockItems = new List + { + new Item { Id = Guid.NewGuid().ToString(), Text = "First item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Second item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Third item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Fourth item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Fifth item", Description="This is an item description." }, + new Item { Id = Guid.NewGuid().ToString(), Text = "Sixth item", Description="This is an item description." }, + }; + + foreach (var item in mockItems) + { + items.Add(item); + } + } + + public async Task AddItemAsync(Item item) + { + items.Add(item); + + return await Task.FromResult(true); + } + + public async Task UpdateItemAsync(Item item) + { + var _item = items.Where((Item arg) => arg.Id == item.Id).FirstOrDefault(); + items.Remove(_item); + items.Add(item); + + return await Task.FromResult(true); + } + + public async Task DeleteItemAsync(string id) + { + var _item = items.Where((Item arg) => arg.Id == id).FirstOrDefault(); + items.Remove(_item); + + return await Task.FromResult(true); + } + + public async Task GetItemAsync(string id) + { + return await Task.FromResult(items.FirstOrDefault(s => s.Id == id)); + } + + public async Task> GetItemsAsync(bool forceRefresh = false) + { + return await Task.FromResult(items); + } + } +} diff --git a/src/UITestDemo/UITestDemo.projitems b/src/UITestDemo/UITestDemo.projitems new file mode 100644 index 0000000..099fdcb --- /dev/null +++ b/src/UITestDemo/UITestDemo.projitems @@ -0,0 +1,44 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {2933EC8A-6E14-4F77-BEF6-51ACEAFD1040} + + + UITestDemo + + + + + + + + + + + + + AboutPage.xaml + + + ItemDetailPage.xaml + + + ItemsPage.xaml + + + NewItemPage.xaml + + + App.xaml + + + + + + + + + + \ No newline at end of file diff --git a/src/UITestDemo/UITestDemo.shproj b/src/UITestDemo/UITestDemo.shproj new file mode 100644 index 0000000..c409d9f --- /dev/null +++ b/src/UITestDemo/UITestDemo.shproj @@ -0,0 +1,11 @@ + + + + {2933EC8A-6E14-4F77-BEF6-51ACEAFD1040} + + + + + + + \ No newline at end of file diff --git a/src/UITestDemo/ViewModels/AboutViewModel.cs b/src/UITestDemo/ViewModels/AboutViewModel.cs new file mode 100644 index 0000000..0c3c366 --- /dev/null +++ b/src/UITestDemo/ViewModels/AboutViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Input; + +using Xamarin.Forms; + +namespace UITestDemo +{ + public class AboutViewModel : BaseViewModel + { + public AboutViewModel() + { + Title = "About"; + + OpenWebCommand = new Command(() => Device.OpenUri(new Uri("https://xamarin.com/platform"))); + } + + public ICommand OpenWebCommand { get; } + } +} diff --git a/src/UITestDemo/ViewModels/BaseViewModel.cs b/src/UITestDemo/ViewModels/BaseViewModel.cs new file mode 100644 index 0000000..e728ffe --- /dev/null +++ b/src/UITestDemo/ViewModels/BaseViewModel.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +using Xamarin.Forms; + +namespace UITestDemo +{ + public class BaseViewModel : INotifyPropertyChanged + { + public IDataStore DataStore => DependencyService.Get>() ?? new MockDataStore(); + + bool isBusy = false; + public bool IsBusy + { + get { return isBusy; } + set { SetProperty(ref isBusy, value); } + } + + string title = string.Empty; + public string Title + { + get { return title; } + set { SetProperty(ref title, value); } + } + + protected bool SetProperty(ref T backingStore, T value, + [CallerMemberName]string propertyName = "", + Action onChanged = null) + { + if (EqualityComparer.Default.Equals(backingStore, value)) + return false; + + backingStore = value; + onChanged?.Invoke(); + OnPropertyChanged(propertyName); + return true; + } + + #region INotifyPropertyChanged + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + var changed = PropertyChanged; + if (changed == null) + return; + + changed.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + #endregion + } +} diff --git a/src/UITestDemo/ViewModels/ItemDetailViewModel.cs b/src/UITestDemo/ViewModels/ItemDetailViewModel.cs new file mode 100644 index 0000000..6aeffb8 --- /dev/null +++ b/src/UITestDemo/ViewModels/ItemDetailViewModel.cs @@ -0,0 +1,14 @@ +using System; + +namespace UITestDemo +{ + public class ItemDetailViewModel : BaseViewModel + { + public Item Item { get; set; } + public ItemDetailViewModel(Item item = null) + { + Title = item?.Text; + Item = item; + } + } +} diff --git a/src/UITestDemo/ViewModels/ItemsViewModel.cs b/src/UITestDemo/ViewModels/ItemsViewModel.cs new file mode 100644 index 0000000..252e634 --- /dev/null +++ b/src/UITestDemo/ViewModels/ItemsViewModel.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Threading.Tasks; + +using Xamarin.Forms; + +namespace UITestDemo +{ + public class ItemsViewModel : BaseViewModel + { + public ObservableCollection Items { get; set; } + public Command LoadItemsCommand { get; set; } + + public ItemsViewModel() + { + Title = "Browse"; + Items = new ObservableCollection(); + LoadItemsCommand = new Command(async () => await ExecuteLoadItemsCommand()); + + MessagingCenter.Subscribe(this, "AddItem", async (obj, item) => + { + var _item = item as Item; + Items.Add(_item); + await DataStore.AddItemAsync(_item); + }); + } + + async Task ExecuteLoadItemsCommand() + { + if (IsBusy) + return; + + IsBusy = true; + + try + { + Items.Clear(); + var items = await DataStore.GetItemsAsync(true); + foreach (var item in items) + { + Items.Add(item); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + finally + { + IsBusy = false; + } + } + } +} diff --git a/src/UITestDemo/Views/AboutPage.xaml b/src/UITestDemo/Views/AboutPage.xaml new file mode 100644 index 0000000..2cf4a9d --- /dev/null +++ b/src/UITestDemo/Views/AboutPage.xaml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + +