Categories
discuss

RuntimeException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots

I am setting up Spoon for Android UI Testing using the gradle-spoon-plugin using the Spoon 2.0.0 snapshot. My project is set up using Android Gradle plug-in 3.0.1.

When taking screenshots via spoonRule.screenshot(activity, "hello"), I get this RuntimeException:

java.lang.RuntimeException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots
at com.squareup.spoon.SpoonRule.createDir(SpoonRule.java:167)
at com.squareup.spoon.SpoonRule.createDir(SpoonRule.java:164)
at com.squareup.spoon.SpoonRule.createDir(SpoonRule.java:164)
at com.squareup.spoon.SpoonRule.obtainDirectory(SpoonRule.java:108)
at com.squareup.spoon.SpoonRule.screenshot(SpoonRule.java:66)

Things work fine if I run it on a Nexus 4 API 19 emulator but it does not work on a Pixel 2 API 27 emulator. Permissions have changed a bunch from 19 to 27 so this is not totally unexpected.

I’ve tried most of the advice currently available including adding a manifest in my androidTest directory that grants the read and write external storage (with and without the maxSdkVersion):

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="tv.twitch.android.test">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>

    <uses-sdk tools:overrideLibrary="android.support.test.uiautomator.v18"/>

</manifest>

I see these permissions being merged into my final manifest of my app’s AndroidManifest (unsure how to check the test app’s manifest) in both cases.

I’ve tried granting permissions via UIAutomator to both the app and test package:

val device = UiDevice.getInstance(getInstrumentation())
device.executeShellCommand("pm grant tv.twitch.android.test android.permission.READ_EXTERNAL_STORAGE")
device.executeShellCommand("pm grant tv.twitch.android.debug android.permission.READ_EXTERNAL_STORAGE")
device.executeShellCommand("pm grant tv.twitch.android.test android.permission.WRITE_EXTERNAL_STORAGE")
device.executeShellCommand("pm grant tv.twitch.android.debug android.permission.WRITE_EXTERNAL_STORAGE")

This outputs a Permission denied to Logcat and results in the same exception above.

If I try leveraging GrantPermissionRule like:

@get:Rule var runtimePermissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)

I get a different exception:

junit.framework.AssertionFailedError: Failed to grant permissions, see logcat for details
at junit.framework.Assert.fail(Assert.java:50)
at android.support.test.runner.permission.PermissionRequester.requestPermissions(PermissionRequester.java:110)
at android.support.test.rule.GrantPermissionRule$RequestPermissionStatement.evaluate(GrantPermissionRule.java:108)

GrantPermissionCallable: Permission: android.permission.WRITE_EXTERNAL_STORAGE cannot be granted!

Removing Manifest.permission.WRITE_EXTERNAL_STORAGE and leaving just the read one gets us back to the original exception: java.lang.RuntimeException: Unable to create output dir: /storage/emulated/0/app_spoon-screenshots

Running from within Android Studio or on the command-line using gradle-spoon-plugin doesn’t affect any of the above.

Looking at the permissions my app and test app are granted in Settings, I see my app has Storage permission but my test app (tv.twitch.android.test) does not have any permissions requested.

I also tried using the Barista library:

PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.WRITE_EXTERNAL_STORAGE)

With no luck their either.

Updates:

I tried making my test app target SDK version 22 in hopes it would get write permissions.

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="tv.twitch.android.test">

    <uses-sdk
        android:minSdkVersion="16"
        android:targetSdkVersion="22"
        tools:overrideLibrary="android.support.test.uiautomator.v18"/>

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

</manifest>

Via Gradle (./gradlew spoon) I tried configuring the gradle-spoon-plugin to request all permissions:

spoon {
    // Grant all runtime permissions during installation on Marshmallow and above devices.
    grantAll = true
}

With no luck. And I even tried using Spoon library version 1.3.1 to no avail as well.

Answer

My issue was caused by an external library merging in the maxSdkVersion with the WRITE_EXTERNAL_STORAGE permission. To get around this I removed the attribute and re-added the WRITE_EXTERNAL_STORAGE permission. This is only needed for my application manifest and not the test application manifest.

<!-- Strip away maxSdkVersion -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 tools:remove="android:maxSdkVersion"/>

<!-- Add the permission with no maxSdkVersion defined -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Then you can build, grant permissions, and run the tests:

# Uninstall existing APKs and install our app APK and test APK
./gradlew uninstallAll installDebug installDebugAndroidTest

# List all APKs installed with adb shell 'pm list packages -f'
# Grant the app APK write and read to external storage permissions
adb shell pm grant gg.mark.debug android.permission.WRITE_EXTERNAL_STORAGE
adb shell pm grant gg.mark.debug android.permission.READ_EXTERNAL_STORAGE

export APK=build/outputs/apk/debug/debug.apk
export TEST_APK=build/outputs/apk/androidTest/debug/debug-androidTest.apk

# TEST_APK and APK are positional arguments so keep them in this order
# Disable GIF generation because it's slow
java -jar spoon-runner-2.0.0.jar --debug --disable-gif "$TEST_APK" "$APK"

The pm grant commands will fail if the permissions for your app are not set up properly in merged AndroidManifest.xml. Make sure those commands succeed!

Categories
discuss

Vue js – Set alt image when image source not found

I am working on a site which has a list of products. Each product has a corresponding image. I am binding the image url to the source attribute like below.

<img :src="product.ImageUrl"/>

If the image is not found, I want to show a default image.

I do as below in cshtml razor syntax (for reference only)

onerror='this.onerror = null;this.src = "@Url.Content("~/images/photo-not-available.jpg")";'

How do I achieve the same in Vue?

Answer

You can set an onerror handler function with @error in Vue:

<img :src="" @error="aVueFunctionThatChangesTheSrc">
Categories
discuss

How to define a LogicalType in Avro. (java)

I need to be able to mark some fields in the AVRO schema so that they will be encrypted at serialization time.

A logicalType allows to mark the fields, and together with a custom conversion should allow to let them be encrypted transparently by AVRO.


I had some issues to find documentation on how to define and use a new logicalType in AVRO (avro_1.8.2#Logical+Types).
I decided then to share here in the answer what I found, to easy the life of anyone else getting on it and to get some feedback in case I’m doing something wrong.

Answer

First of all I defined a logicalType as:

public class EncryptedLogicalType extends LogicalType {
    //The key to use as a reference to the type
    public static final String ENCRYPTED_LOGICAL_TYPE_NAME = "encrypted";

    EncryptedLogicalType() {
        super(ENCRYPTED_LOGICAL_TYPE_NAME);
    }

    @Override
    public void validate(Schema schema) {
        super.validate(schema);
        if (schema.getType() != Schema.Type.BYTES) {
            throw new IllegalArgumentException(
                    "Logical type 'encrypted' must be backed by bytes");
        }
    }
}

Then a new conversion:

public class EncryptedConversion extends Conversion<ByteBuffer> {
    // Construct a unique instance for all the conversion. This have to be changed in case the conversion
    //   needs some runtime information (e.g.: an encryption key / a tenant_ID). If so, the get() method should 
    //   return the appropriate conversion per key.
    private static final EncryptedConversion INSTANCE = new EncryptedConversion();
    public static final EncryptedConversion get(){ return INSTANCE; }
    private EncryptedConversion(){ super(); }

    //This conversion operates on ByteBuffer and returns ByteBuffer
    @Override
    public Class<ByteBuffer> getConvertedType() { return ByteBuffer.class; }

    @Override
    public String getLogicalTypeName() { return EncryptedLogicalType.ENCRYPTED_LOGICAL_TYPE_NAME; }

    // fromBytes and toBytes have to be overridden as this conversion works on bytes. Other may need to be 
    //  overridden. The types supported need to be updated also in EncryptedLogicalType#validate(Schema schema)
    @Override
    public ByteBuffer fromBytes(ByteBuffer value, Schema schema, LogicalType type) {
        encryptedValue = __encryptionLogic__(value); 
        return encryptedValue;
    }

    @Override
    public ByteBuffer toBytes(ByteBuffer value, Schema schema, LogicalType type) {
        decryptedValue = __decryptionLogic__(value); 
        return decryptedValue;
    }
}

The .avsc schema file will be similar to:

{
    "name": “MyMessageWithEncryptedField”,
    "type": "record",
    "fields": [
        {"name": "payload","type" : {"type" : "bytes","logicalType" : "encrypted"}},
        ...

Finally in the MyMessageWithEncryptedField.java class generated out of the schema file I added the method to return the conversion:

@Override
public Conversion<?> getConversion(int fieldIndex) {
    // This allow us to have a more flexible conversion retrieval, so we don't have to code it per field.
    Schema fieldSchema = SCHEMA$.getFields().get(fieldIndex).schema();
    if ((fieldSchema.getLogicalType() != null)
            && (fieldSchema.getLogicalType().getName() == EncryptedLogicalType.ENCRYPTED_LOGICAL_TYPE_NAME)){
     // here we could pass to the get() method a runtime information, e.g.: a tenantId that can be found in the data structure.
        return EncryptedConversion.get();
    }
    return null;
}

To make it run I still have to register the type at runtime:

LogicalTypes.register(EncryptedLogicalType.ENCRYPTED_LOGICAL_TYPE_NAME, new LogicalTypes.LogicalTypeFactory() {
    private final LogicalType encryptedLogicalType = new EncryptedLogicalType();
    @Override
    public LogicalType fromSchema(Schema schema) {
        return encryptedLogicalType;
    }
});

Few notes:

  • if your logicalType needs some other properties passed in from the schema definition, you can modify the LogicalType class taking example from avro.lang.java.avro.src.main.java.org.apache.avro.LogicalTypes.Decimal
  • the last piece of code (the register) is currently run before my logic starts, but I plan to move it in a static block inside the schema generated class (MyMessageWithEncryptedField.java)
Categories
discuss

Vis.js network: how to add a node on click inside the canvas?

Manipulation methods of vis.js only include addNodeMode(), but not something like addNode(). I wonder if there’s some nice way to create a node on click. May be by manipulating the data instead of network itself?

Of’course, one may go

network.on('click',function(params){
    if((params.nodes.length == 0) && (params.edges.length == 0)) {
        network.addNodeMode(); // doesn't add, one more click needed
        //# generate click in the same place. Use params.pointer.canvas
        //  or params.pointer.DOM to set appropriate coordinates
    }
})

but then we have also to prevent infinit loops since we generate a click event in a click handler..

Answer

Ok, here’s my current implementation:

...
data = ...
nodes = new vis.DataSet(data.nodes); // make nodes manipulatable
data = { nodes:nodes, edges:edges };
...
var network = new vis.Network(container, data, options);

network.on('click',function(params){
    if((params.nodes.length == 0) && (params.edges.length == 0)) {
        var updatedIds = nodes.add([{
            label:'new',
            x:params.pointer.canvas.x,
            y:params.pointer.canvas.y
        }]);
        network.selectNodes([updatedIds[0]]);
        network.editNode();
    }
})

It’s not perfect since it actually creates a node and starts editing it, so if we cancel editing, the node stays. It also creates unwanted shadows of nodes. But it’s already a working prototype which is enough to start with.

Categories
discuss

Vee-Validate validateAll() with scope

I have a scenario where I have sectioned out (scoped) a form so that I can validate small chunks at a time using the following function.

validateScope (scope) {
  return this.$validator.validateAll(scope);
}

I want to do one final validation of the entire form before I submit it to the server; however, validateAll() doesn’t seem to pick up inputs that have been added to a scope. I’ve also tried just validating each scope and then submit the form if they are ALL valid, but I am not sure how to do that since everything is asynchronous.

validateAll () {
   let valid = true;

   // Not sure how to build this function since validateScope is asynchronous
   _.each(this.names, (name, index) => {
     if(this.validateScope('name-' + index)){
       valid = false;
     }
   });

   return valid; // Always returns true even though the _.each should set it to false
}

Answer

As mentioned in my comment, your code will end up looking something like this:

validateAll () {
   let valid = true;

   let validations = []
   _.each(this.names, (name, index) => {
     validations.push(this.validateScope('name-' + index))
   });

   return Promise.all(validations)
     // consolidate the results into one Boolean
     .then(results => results.every(r => r))
}

Then, of course, you’ll have to use validateAll as a promise:

this.validateAll().then(isValid => {
  if (!isValid) {
    //do whatever you need to do when something failed validation
  } else {
    // do whatever you need to do when everything is valid here
  }
})
Source: stackoverflow
Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By using this site, you agree to the Privacy Policy, and Copyright Policy. Content is available under CC BY-SA 3.0 unless otherwise noted. The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 © No Copyrights, All Questions are retrived from public domain..